Merge last green PGO from mozilla-inbound to mozilla-central
authorMarco Bonardo <mbonardo@mozilla.com>
Fri, 06 Jan 2012 13:22:51 +0100
changeset 86335 fcc32e70c95fdbb491100259f35742d1b45c0cd9
parent 86312 8ae16e346bd0c2c93711884b2a2e5db10060512d (current diff)
parent 86334 0ac1cbff2a676dd6312e9ee2dcd813e14645fcc8 (diff)
child 86336 af6501ede37845e43374df21326ae8550696d053
child 86374 11ab4e9dff6b04139f86cfab4070cdff858c0c3e
push idunknown
push userunknown
push dateunknown
milestone12.0a1
Merge last green PGO from mozilla-inbound to mozilla-central
gfx/thebes/gfxTextRunCache.cpp
gfx/thebes/gfxTextRunCache.h
gfx/thebes/gfxTextRunWordCache.cpp
gfx/thebes/gfxTextRunWordCache.h
layout/base/nsPresShell.cpp
--- a/build/mobile/devicemanagerADB.py
+++ b/build/mobile/devicemanagerADB.py
@@ -107,27 +107,31 @@ class DeviceManagerADB(DeviceManager):
   #  success: remoteDir
   #  failure: None
   def pushDir(self, localDir, remoteDir):
     # adb "push" accepts a directory as an argument, but if the directory
     # contains symbolic links, the links are pushed, rather than the linked
     # files; we either zip/unzip or push file-by-file to get around this 
     # limitation
     try:
+      if (not self.dirExists(remoteDir)):
+        self.mkDirs(remoteDir+"/x")
       if (self.useZip):
         localZip = tempfile.mktemp()+".zip"
         remoteZip = remoteDir + "/adbdmtmp.zip"
         subprocess.check_output(["zip", "-r", localZip, '.'], cwd=localDir)
         self.pushFile(localZip, remoteZip)
         os.remove(localZip)
-        self.checkCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir])
+        data = self.runCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir]).stdout.read()
         self.checkCmdAs(["shell", "rm", remoteZip])
+        if (re.search("unzip: exiting", data) or re.search("Operation not permitted", data)):
+          print "zip/unzip failure: falling back to normal push"
+          self.useZip = False
+          self.pushDir(localDir, remoteDir)
       else:
-        if (not self.dirExists(remoteDir)):
-          self.mkDirs(remoteDir+"/x")
         for root, dirs, files in os.walk(localDir, followlinks='true'):
           relRoot = os.path.relpath(root, localDir)
           for file in files:
             localFile = os.path.join(root, file)
             remoteFile = remoteDir + "/"
             if (relRoot!="."):
               remoteFile = remoteFile + relRoot + "/"
             remoteFile = remoteFile + file
@@ -604,17 +608,17 @@ class DeviceManagerADB(DeviceManager):
       if (self.fileExists(devroot + "/sanity/tmpfile")):
         print "will execute commands via run-as " + packageName
         self.packageName = packageName
         self.useRunAs = True
       self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"])
       self.checkCmd(["shell", "run-as", packageName, "rm", "-r", devroot + "/sanity"])
       
   def isUnzipAvailable(self):
-    data = self.runCmd(["shell", "unzip"]).stdout.read()
+    data = self.runCmdAs(["shell", "unzip"]).stdout.read()
     if (re.search('Usage', data)):
       return True
     else:
       return False
 
   def isLocalZipAvailable(self):
     try:
       subprocess.check_call(["zip", "-?"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
--- a/build/mobile/robocop/FennecNativeElement.java.in
+++ b/build/mobile/robocop/FennecNativeElement.java.in
@@ -136,14 +136,34 @@ public class FennecNativeElement impleme
       e.printStackTrace();
     }
     if(text == null) {
       throw new RoboCopException("getText: Text is null for view "+id);
     }
     return text.toString();
   }
 
+  private boolean displayed;
+
   @Override
   public boolean isDisplayed() {
-    // TODO Auto-generated method stub
-    return false;
+    final SynchronousQueue syncQueue = new SynchronousQueue();
+    currentActivity = robocop.getCurrentActivity();
+    displayed = false;
+    currentActivity.runOnUiThread(
+        new Runnable() {
+          public void run() {
+            View view = (View)currentActivity.findViewById(id);
+            if(view != null) {
+              displayed = true;
+            }
+            syncQueue.offer(new Object());
+          }
+        });
+    try {
+      syncQueue.take();
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+    return displayed;
   }
+
 }
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -92,17 +92,16 @@
 
 #include "imgIEncoder.h"
 
 #include "gfxContext.h"
 #include "gfxASurface.h"
 #include "gfxImageSurface.h"
 #include "gfxPlatform.h"
 #include "gfxFont.h"
-#include "gfxTextRunCache.h"
 #include "gfxBlur.h"
 #include "gfxUtils.h"
 
 #include "nsFrameManager.h"
 #include "nsFrameLoader.h"
 #include "nsBidi.h"
 #include "nsBidiPresUtils.h"
 #include "Layers.h"
@@ -2732,22 +2731,21 @@ nsCanvasRenderingContext2D::MeasureText(
 
 /**
  * Used for nsBidiPresUtils::ProcessText
  */
 struct NS_STACK_CLASS nsCanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
 {
     virtual void SetText(const PRUnichar* text, PRInt32 length, nsBidiDirection direction)
     {
-        mTextRun = gfxTextRunCache::MakeTextRun(text,
-                                                length,
-                                                mFontgrp,
-                                                mThebes,
-                                                mAppUnitsPerDevPixel,
-                                                direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
+        mTextRun = mFontgrp->MakeTextRun(text,
+                                         length,
+                                         mThebes,
+                                         mAppUnitsPerDevPixel,
+                                         direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
     }
 
     virtual nscoord GetWidth()
     {
         gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0,
                                                                    mTextRun->GetLength(),
                                                                    mDoMeasureBoundingBox ?
                                                                        gfxFont::TIGHT_INK_EXTENTS :
@@ -2806,17 +2804,17 @@ struct NS_STACK_CLASS nsCanvasBidiProces
                            point,
                            0,
                            mTextRun->GetLength(),
                            nsnull,
                            nsnull);
     }
 
     // current text run
-    gfxTextRunCache::AutoTextRun mTextRun;
+    nsAutoPtr<gfxTextRun> mTextRun;
 
     // pointer to the context, may not be the canvas's context
     // if an intermediate surface is being used
     gfxContext* mThebes;
 
     // position of the left side of the string, alphabetic baseline
     gfxPoint mPt;
 
@@ -3150,18 +3148,18 @@ gfxTextRun*
 nsCanvasRenderingContext2D::MakeTextRun(const PRUnichar* aText,
                                         PRUint32         aLength,
                                         PRUint32         aAppUnitsPerDevUnit,
                                         PRUint32         aFlags)
 {
     gfxFontGroup* currentFontStyle = GetCurrentFontStyle();
     if (!currentFontStyle)
         return nsnull;
-    return gfxTextRunCache::MakeTextRun(aText, aLength, currentFontStyle,
-                                        mThebes, aAppUnitsPerDevUnit, aFlags);
+    return currentFontStyle->MakeTextRun(aText, aLength,
+                                         mThebes, aAppUnitsPerDevUnit, aFlags);
 }
 
 
 //
 // line caps/joins
 //
 NS_IMETHODIMP
 nsCanvasRenderingContext2D::SetLineWidth(float width)
--- a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
@@ -89,17 +89,16 @@
 
 #include "imgIEncoder.h"
 
 #include "gfxContext.h"
 #include "gfxASurface.h"
 #include "gfxImageSurface.h"
 #include "gfxPlatform.h"
 #include "gfxFont.h"
-#include "gfxTextRunCache.h"
 #include "gfxBlur.h"
 #include "gfxUtils.h"
 
 #include "nsFrameManager.h"
 #include "nsFrameLoader.h"
 #include "nsBidi.h"
 #include "nsBidiPresUtils.h"
 #include "Layers.h"
@@ -2937,22 +2936,21 @@ nsCanvasRenderingContext2DAzure::Measure
  * Used for nsBidiPresUtils::ProcessText
  */
 struct NS_STACK_CLASS nsCanvasBidiProcessorAzure : public nsBidiPresUtils::BidiProcessor
 {
   typedef nsCanvasRenderingContext2DAzure::ContextState ContextState;
 
   virtual void SetText(const PRUnichar* text, PRInt32 length, nsBidiDirection direction)
   {
-    mTextRun = gfxTextRunCache::MakeTextRun(text,
-                                            length,
-                                            mFontgrp,
-                                            mThebes,
-                                            mAppUnitsPerDevPixel,
-                                            direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
+    mTextRun = mFontgrp->MakeTextRun(text,
+                                     length,
+                                     mThebes,
+                                     mAppUnitsPerDevPixel,
+                                     direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
   }
 
   virtual nscoord GetWidth()
   {
     gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0,
                                                                mTextRun->GetLength(),
                                                                mDoMeasureBoundingBox ?
                                                                  gfxFont::TIGHT_INK_EXTENTS :
@@ -3091,17 +3089,17 @@ struct NS_STACK_CLASS nsCanvasBidiProces
                                 state.dashOffset),
                   DrawOptions(state.globalAlpha, mCtx->UsedOperation()));
 
       }
     }
   }
 
   // current text run
-  gfxTextRunCache::AutoTextRun mTextRun;
+  nsAutoPtr<gfxTextRun> mTextRun;
 
   // pointer to a screen reference context used to measure text and such
   nsRefPtr<gfxContext> mThebes;
 
   // Pointer to the draw target we should fill our text to
   nsCanvasRenderingContext2DAzure *mCtx;
 
   // position of the left side of the string, alphabetic baseline
--- a/editor/libeditor/text/nsTextEditRules.cpp
+++ b/editor/libeditor/text/nsTextEditRules.cpp
@@ -939,27 +939,23 @@ nsTextEditRules::WillUndo(nsISelection *
  */
 nsresult
 nsTextEditRules::DidUndo(nsISelection *aSelection, nsresult aResult)
 {
   nsresult res = aResult;  // if aResult is an error, we return it.
   if (!aSelection) { return NS_ERROR_NULL_POINTER; }
   if (NS_SUCCEEDED(res)) 
   {
-    if (mBogusNode) {
-      mBogusNode = nsnull;
-    }
+    nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot());
+    NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
+    nsCOMPtr<nsIDOMNode> node = mEditor->GetLeftmostChild(theRoot);
+    if (node && mEditor->IsMozEditorBogusNode(node))
+      mBogusNode = node;
     else
-    {
-      nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot());
-      NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
-      nsCOMPtr<nsIDOMNode> node = mEditor->GetLeftmostChild(theRoot);
-      if (node && mEditor->IsMozEditorBogusNode(node))
-        mBogusNode = node;
-    }
+      mBogusNode = nsnull;
   }
   return res;
 }
 
 nsresult
 nsTextEditRules::WillRedo(nsISelection *aSelection, bool *aCancel, bool *aHandled)
 {
   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
@@ -972,40 +968,41 @@ nsTextEditRules::WillRedo(nsISelection *
 
 nsresult
 nsTextEditRules::DidRedo(nsISelection *aSelection, nsresult aResult)
 {
   nsresult res = aResult;  // if aResult is an error, we return it.
   if (!aSelection) { return NS_ERROR_NULL_POINTER; }
   if (NS_SUCCEEDED(res)) 
   {
-    if (mBogusNode) {
-      mBogusNode = nsnull;
-    }
-    else
+    nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot());
+    NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
+    
+    nsCOMPtr<nsIDOMNodeList> nodeList;
+    res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"),
+                                        getter_AddRefs(nodeList));
+    NS_ENSURE_SUCCESS(res, res);
+    if (nodeList)
     {
-      nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot());
-      NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
+      PRUint32 len;
+      nodeList->GetLength(&len);
       
-      nsCOMPtr<nsIDOMNodeList> nodeList;
-      res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"),
-                                          getter_AddRefs(nodeList));
-      NS_ENSURE_SUCCESS(res, res);
-      if (nodeList)
-      {
-        PRUint32 len;
-        nodeList->GetLength(&len);
-        
-        if (len != 1) return NS_OK;  // only in the case of one br could there be the bogus node
-        nsCOMPtr<nsIDOMNode> node;
-        nodeList->Item(0, getter_AddRefs(node));
-        NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
-        if (mEditor->IsMozEditorBogusNode(node))
-          mBogusNode = node;
+      if (len != 1) {
+        // only in the case of one br could there be the bogus node
+        mBogusNode = nsnull;
+        return NS_OK;  
       }
+
+      nsCOMPtr<nsIDOMNode> node;
+      nodeList->Item(0, getter_AddRefs(node));
+      NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
+      if (mEditor->IsMozEditorBogusNode(node))
+        mBogusNode = node;
+      else
+        mBogusNode = nsnull;
     }
   }
   return res;
 }
 
 nsresult
 nsTextEditRules::WillOutputText(nsISelection *aSelection, 
                                 const nsAString  *aOutputFormat,
--- a/editor/libeditor/text/tests/Makefile.in
+++ b/editor/libeditor/text/tests/Makefile.in
@@ -71,16 +71,17 @@ include $(topsrcdir)/config/rules.mk
 # on our editor, and the combinations depend on the system.
 ifneq ($(MOZ_WIDGET_TOOLKIT),gtk2)
 _TEST_FILES += \
 		test_texteditor_keyevent_handling.html \
 		$(NULL)
 endif
 
 _CHROME_TEST_FILES = \
+		test_bug471319.html \
 		test_bug483651.html \
 		test_bug636465.xul \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
 
 libs:: $(_CHROME_TEST_FILES)
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/text/tests/test_bug471319.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is Plaintext Editor Test code
+   -
+   - The Initial Developer of the Original Code is
+   - Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>.
+   - Portions created by the Initial Developer are Copyright (C) 2011
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=471319
+-->
+
+<head>
+  <title>Test for Bug 471319</title>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+
+<body onload="doTest();">
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=471319">Mozilla Bug 471319</a>
+  <p id="display"></p>
+  <div id="content" style="display: none">
+  </div>
+
+  <pre id="test">
+    <script type="application/javascript;version=1.7">
+
+      /** Test for Bug 471319 **/
+    
+      SimpleTest.waitForExplicitFinish();
+
+      function doTest() {
+        let t1 = $("t1");
+        let editor = null;
+
+        // Test 1: Undo on an empty editor - the editor should not forget about
+        // the bogus node
+        t1.QueryInterface(Components.interfaces.nsIDOMNSEditableElement);
+        t1Editor = t1.editor;
+
+        // Did the editor recognize the new bogus node?
+        t1Editor.undo(1);
+        ok(!t1.value, "<br> still recognized as bogus node on undo");
+
+
+        // Test 2: Redo on an empty editor - the editor should not forget about
+        // the bogus node
+        let t2 = $("t2");
+        t2.QueryInterface(Components.interfaces.nsIDOMNSEditableElement);
+        t2Editor = t2.editor;
+
+        // Did the editor recognize the new bogus node?
+        t2Editor.redo(1);
+        ok(!t2.value, "<br> still recognized as bogus node on redo");
+
+
+        // Test 3: Undoing a batched transaction where both end points of the
+        // transaction are the bogus node - the bogus node should still be
+        // recognized as bogus
+        t1Editor.transactionManager.beginBatch();
+        t1.value = "mozilla";
+        t1.value = "";
+        t1Editor.transactionManager.endBatch();
+        t1Editor.undo(1);
+        ok(!t1.value,
+           "recreated <br> from undo transaction recognized as bogus");
+
+
+        // Test 4: Redoing a batched transaction where both end points of the
+        // transaction are the bogus node - the bogus node should still be
+        // recognized as bogus
+        t1Editor.redo(1);
+        ok(!t1.value,
+           "recreated <br> from redo transaction recognized as bogus");
+        SimpleTest.finish();
+     }
+   </script>
+  </pre>
+
+  <input type="text" id="t1" />
+  <input type="text" id="t2" />
+</body>
+</html>
--- a/gfx/src/nsFontMetrics.cpp
+++ b/gfx/src/nsFontMetrics.cpp
@@ -36,48 +36,54 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsFontMetrics.h"
 #include "nsBoundingMetrics.h"
 #include "nsRenderingContext.h"
 #include "nsDeviceContext.h"
 #include "nsStyleConsts.h"
-#include "gfxTextRunCache.h"
 
 namespace {
 
-class AutoTextRun : public gfxTextRunCache::AutoTextRun {
+class AutoTextRun {
 public:
     AutoTextRun(nsFontMetrics* aMetrics, nsRenderingContext* aRC,
                 const char* aString, PRInt32 aLength)
-        : gfxTextRunCache::AutoTextRun(gfxTextRunCache::MakeTextRun(
+    {
+        mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
             reinterpret_cast<const PRUint8*>(aString), aLength,
-            aMetrics->GetThebesFontGroup(), aRC->ThebesContext(),
+            aRC->ThebesContext(),
             aMetrics->AppUnitsPerDevPixel(),
-            ComputeFlags(aMetrics)))
-    {}
+            ComputeFlags(aMetrics));
+    }
 
     AutoTextRun(nsFontMetrics* aMetrics, nsRenderingContext* aRC,
                 const PRUnichar* aString, PRInt32 aLength)
-        : gfxTextRunCache::AutoTextRun(gfxTextRunCache::MakeTextRun(
-            aString, aLength, aMetrics->GetThebesFontGroup(),
+    {
+        mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
+            aString, aLength,
             aRC->ThebesContext(),
             aMetrics->AppUnitsPerDevPixel(),
-            ComputeFlags(aMetrics)))
-    {}
+            ComputeFlags(aMetrics));
+    }
+
+    gfxTextRun *get() { return mTextRun; }
+    gfxTextRun *operator->() { return mTextRun; }
 
 private:
     static PRUint32 ComputeFlags(nsFontMetrics* aMetrics) {
         PRUint32 flags = 0;
         if (aMetrics->GetTextRunRTL()) {
             flags |= gfxTextRunFactory::TEXT_IS_RTL;
         }
         return flags;
     }
+
+    nsAutoPtr<gfxTextRun> mTextRun;
 };
 
 class StubPropertyProvider : public gfxTextRun::PropertyProvider {
 public:
     virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
                                       bool* aBreakBefore) {
         NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText");
     }
--- a/gfx/tests/gfxFontSelectionTest.cpp
+++ b/gfx/tests/gfxFontSelectionTest.cpp
@@ -40,17 +40,16 @@
 #include "nsString.h"
 #include "nsDependentString.h"
 
 #include "mozilla/Preferences.h"
 
 #include "gfxContext.h"
 #include "gfxFont.h"
 #include "gfxPlatform.h"
-#include "gfxTextRunWordCache.h"
 
 #include "gfxFontTest.h"
 
 #if defined(XP_MACOSX)
 #include "gfxTestCocoaHelper.h"
 #endif
 
 #ifdef MOZ_WIDGET_GTK2
@@ -61,18 +60,16 @@ using namespace mozilla;
 
 enum {
     S_UTF8 = 0,
     S_ASCII = 1
 };
 
 class FrameTextRunCache;
 
-static gfxTextRunWordCache *gTextRunCache;
-
 struct LiteralArray {
     LiteralArray (unsigned long l1) {
         data.AppendElement(l1);
     }
     LiteralArray (unsigned long l1, unsigned long l2) {
         data.AppendElement(l1);
         data.AppendElement(l2);
     }
@@ -299,21 +296,21 @@ RunTest (TestEntry *test, gfxContext *ct
     PRUint32 flags = gfxTextRunFactory::TEXT_IS_PERSISTENT;
     if (test->isRTL) {
         flags |= gfxTextRunFactory::TEXT_IS_RTL;
     }
     PRUint32 length;
     if (test->stringType == S_ASCII) {
         flags |= gfxTextRunFactory::TEXT_IS_ASCII | gfxTextRunFactory::TEXT_IS_8BIT;
         length = strlen(test->string);
-        textRun = gfxTextRunWordCache::MakeTextRun(reinterpret_cast<const PRUint8*>(test->string), length, fontGroup, &params, flags);
+        textRun = fontGroup->MakeTextRun(reinterpret_cast<const PRUint8*>(test->string), length, &params, flags);
     } else {
         NS_ConvertUTF8toUTF16 str(nsDependentCString(test->string));
         length = str.Length();
-        textRun = gfxTextRunWordCache::MakeTextRun(str.get(), length, fontGroup, &params, flags);
+        textRun = fontGroup->MakeTextRun(str.get(), length, &params, flags);
     }
 
     gfxFontTestStore::NewStore();
     textRun->Draw(ctx, gfxPoint(0,0), 0, length, nsnull, nsnull);
     gfxFontTestStore *s = gfxFontTestStore::CurrentStore();
 
     gTextRunCache->RemoveTextRun(textRun);
 
@@ -343,18 +340,16 @@ main (int argc, char **argv) {
     nsresult rv = NS_InitXPCOM2(nsnull, nsnull, nsnull);
     if (NS_FAILED(rv))
         return -1;
 
     rv = gfxPlatform::Init();
     if (NS_FAILED(rv))
         return -1;
 
-    gTextRunCache = new gfxTextRunWordCache();
-
     // let's get all the xpcom goop out of the system
     fflush (stderr);
     fflush (stdout);
 
     // don't need to query, we might need to set up some prefs later
     if (0) {
         nsresult rv;
 
--- a/gfx/tests/gfxWordCacheTest.cpp
+++ b/gfx/tests/gfxWordCacheTest.cpp
@@ -43,18 +43,16 @@
 #include "prinrval.h"
 
 #include "gfxContext.h"
 #include "gfxFont.h"
 #include "gfxPlatform.h"
 
 #include "gfxFontTest.h"
 
-#include "gfxTextRunWordCache.h"
-
 #if defined(XP_MACOSX)
 #include "gfxTestCocoaHelper.h"
 #endif
 
 #ifdef MOZ_WIDGET_GTK2
 #include "gtk/gtk.h"
 #endif
 
@@ -73,17 +71,16 @@ public:
  ~FrameTextRunCache() {
    AgeAllGenerations();
  }
 
  void RemoveFromCache(gfxTextRun* aTextRun) {
    if (aTextRun->GetExpirationState()->IsTracked()) {
      RemoveObject(aTextRun);
    }
-   gfxTextRunWordCache::RemoveTextRun(aTextRun);
  }
 
  // This gets called when the timeout has expired on a gfxTextRun
  virtual void NotifyExpired(gfxTextRun* aTextRun) {
    RemoveFromCache(aTextRun);
    delete aTextRun;
  }
 };
@@ -94,18 +91,17 @@ MakeTextRun(const PRUnichar *aText, PRUi
            PRUint32 aFlags)
 {
    nsAutoPtr<gfxTextRun> textRun;
    if (aLength == 0) {
        textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags);
    } else if (aLength == 1 && aText[0] == ' ') {
        textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags);
    } else {
-       textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
-           aParams, aFlags);
+       textRun = aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags);
    }
    if (!textRun)
        return nsnull;
    nsresult rv = gTextRuns->AddObject(textRun);
    if (NS_FAILED(rv)) {
        gTextRuns->RemoveFromCache(textRun);
        return nsnull;
    }
--- a/gfx/thebes/Makefile.in
+++ b/gfx/thebes/Makefile.in
@@ -70,18 +70,16 @@ EXPORTS	= \
 	gfxPoint3D.h \
 	gfxPointH3D.h \
 	gfxQuad.h \
 	gfxQuaternion.h \
 	gfxRect.h \
 	gfxSkipChars.h \
 	gfxTeeSurface.h \
 	gfxTypes.h \
-	gfxTextRunCache.h \
-	gfxTextRunWordCache.h \
 	gfxUnicodeProperties.h \
 	gfxUtils.h \
 	gfxUserFontSet.h \
 	nsCoreAnimationSupport.h \
 	gfxSharedImageSurface.h \
 	$(NULL)
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),android)
@@ -197,18 +195,16 @@ CPPSRCS	= \
 	gfxMatrix.cpp \
 	gfxPath.cpp \
 	gfxPattern.cpp \
 	gfxPlatform.cpp \
 	gfxPlatformFontList.cpp \
 	gfxRect.cpp \
 	gfxSkipChars.cpp \
 	gfxTeeSurface.cpp \
-	gfxTextRunCache.cpp \
-	gfxTextRunWordCache.cpp \
 	gfxUserFontSet.cpp \
 	gfxUtils.cpp \
 	gfxUnicodeProperties.cpp \
 	gfxScriptItemizer.cpp \
 	gfxHarfBuzzShaper.cpp \
 	gfxSharedImageSurface.cpp \
 	$(NULL)
 
--- a/gfx/thebes/gfxCoreTextShaper.cpp
+++ b/gfx/thebes/gfxCoreTextShaper.cpp
@@ -103,79 +103,69 @@ gfxCoreTextShaper::~gfxCoreTextShaper()
         ::CFRelease(mAttributesDict);
     }
     if (mCTFont) {
         ::CFRelease(mCTFont);
     }
 }
 
 bool
-gfxCoreTextShaper::InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript)
+gfxCoreTextShaper::ShapeWord(gfxContext      *aContext,
+                             gfxShapedWord   *aShapedWord,
+                             const PRUnichar *aText)
 {
-    // aRunStart and aRunLength define the section of the textRun and of aString
-    // that is to be drawn with this particular font
-
-    bool disableLigatures = (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0;
-
     // Create a CFAttributedString with text and style info, so we can use CoreText to lay it out.
 
-    bool isRTL = aTextRun->IsRightToLeft();
+    bool isRightToLeft = aShapedWord->IsRightToLeft();
+    PRUint32 length = aShapedWord->Length();
 
     // we need to bidi-wrap the text if the run is RTL,
     // or if it is an LTR run but may contain (overridden) RTL chars
-    bool bidiWrap = isRTL;
-    if (!bidiWrap && (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) == 0) {
+    bool bidiWrap = isRightToLeft;
+    if (!bidiWrap && !aShapedWord->TextIs8Bit()) {
+        const PRUnichar *text = aShapedWord->TextUnicode();
         PRUint32 i;
-        for (i = aRunStart; i < aRunStart + aRunLength; ++i) {
-            if (gfxFontUtils::PotentialRTLChar(aString[i])) {
+        for (i = 0; i < length; ++i) {
+            if (gfxFontUtils::PotentialRTLChar(text[i])) {
                 bidiWrap = true;
                 break;
             }
         }
     }
 
     // If there's a possibility of any bidi, we wrap the text with direction overrides
     // to ensure neutrals or characters that were bidi-overridden in HTML behave properly.
     const UniChar beginLTR[]    = { 0x202d, 0x20 };
     const UniChar beginRTL[]    = { 0x202e, 0x20 };
     const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c };
 
     PRUint32 startOffset;
     CFStringRef stringObj;
     if (bidiWrap) {
-        startOffset = isRTL ?
-            sizeof(beginRTL) / sizeof(beginRTL[0]) : sizeof(beginLTR) / sizeof(beginLTR[0]);
+        startOffset = isRightToLeft ?
+            mozilla::ArrayLength(beginRTL) : mozilla::ArrayLength(beginLTR);
         CFMutableStringRef mutableString =
             ::CFStringCreateMutable(kCFAllocatorDefault,
-                                    aRunLength + startOffset +
-                                    sizeof(endBidiWrap) / sizeof(endBidiWrap[0]));
+                                    length + startOffset + mozilla::ArrayLength(endBidiWrap));
         ::CFStringAppendCharacters(mutableString,
-                                   isRTL ? beginRTL : beginLTR,
+                                   isRightToLeft ? beginRTL : beginLTR,
                                    startOffset);
-        ::CFStringAppendCharacters(mutableString,
-                                   aString + aRunStart, aRunLength);
+        ::CFStringAppendCharacters(mutableString, aText, length);
         ::CFStringAppendCharacters(mutableString,
-                                   endBidiWrap,
-                                   sizeof(endBidiWrap) / sizeof(endBidiWrap[0]));
+                                   endBidiWrap, mozilla::ArrayLength(endBidiWrap));
         stringObj = mutableString;
     } else {
         startOffset = 0;
         stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
-                                                         aString + aRunStart,
-                                                         aRunLength,
+                                                         aText, length,
                                                          kCFAllocatorNull);
     }
 
     CFDictionaryRef attrObj;
-    if (disableLigatures) {
+    if (aShapedWord->DisableLigatures()) {
         // For letterspacing (or maybe other situations) we need to make a copy of the CTFont
         // with the ligature feature disabled
         CTFontRef ctFont =
             CreateCTFontWithDisabledLigatures(::CTFontGetSize(mCTFont));
 
         attrObj =
             ::CFDictionaryCreate(kCFAllocatorDefault,
                                  (const void**) &kCTFontAttributeName,
@@ -204,69 +194,64 @@ gfxCoreTextShaper::InitTextRun(gfxContex
     CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line);
     PRUint32 numRuns = ::CFArrayGetCount(glyphRuns);
 
     // Iterate through the glyph runs.
     // Note that this includes the bidi wrapper, so we have to be careful
     // not to include the extra glyphs from there
     bool success = true;
     for (PRUint32 runIndex = 0; runIndex < numRuns; runIndex++) {
-        CTRunRef aCTRun = (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex);
-        if (SetGlyphsFromRun(aTextRun, aCTRun, startOffset,
-                             aRunStart, aRunLength) != NS_OK) {
+        CTRunRef aCTRun =
+            (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex);
+        if (SetGlyphsFromRun(aShapedWord, aCTRun, startOffset) != NS_OK) {
             success = false;
             break;
         }
     }
 
     ::CFRelease(line);
 
     return success;
 }
 
 #define SMALL_GLYPH_RUN 128 // preallocated size of our auto arrays for per-glyph data;
                             // some testing indicates that 90%+ of glyph runs will fit
                             // without requiring a separate allocation
 
 nsresult
-gfxCoreTextShaper::SetGlyphsFromRun(gfxTextRun *aTextRun,
+gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedWord *aShapedWord,
                                     CTRunRef aCTRun,
-                                    PRInt32 aStringOffset, // offset in the string used to build the CTLine
-                                    PRInt32 aRunStart,     // starting offset of this font run in the gfxTextRun
-                                    PRInt32 aRunLength)    // length of this font run in characters
+                                    PRInt32 aStringOffset)
 {
-    // The textRun has been bidi-wrapped; aStringOffset is the number
+    // The word has been bidi-wrapped; aStringOffset is the number
     // of chars at the beginning of the CTLine that we should skip.
-    // aRunStart and aRunLength define the range of characters
-    // within the textRun that are "real" data we need to handle.
     // aCTRun is a glyph run from the CoreText layout process.
 
-    bool isLTR = !aTextRun->IsRightToLeft();
-    PRInt32 direction = isLTR ? 1 : -1;
+    PRInt32 direction = aShapedWord->IsRightToLeft() ? -1 : 1;
 
     PRInt32 numGlyphs = ::CTRunGetGlyphCount(aCTRun);
     if (numGlyphs == 0) {
         return NS_OK;
     }
 
+    PRInt32 wordLength = aShapedWord->Length();
+
     // character offsets get really confusing here, as we have to keep track of
     // (a) the text in the actual textRun we're constructing
-    // (b) the "font run" being rendered with the current font, defined by aRunStart and aRunLength
-    //     parameters to InitTextRun
     // (c) the string that was handed to CoreText, which contains the text of the font run
     //     plus directional-override padding
     // (d) the CTRun currently being processed, which may be a sub-run of the CoreText line
     //     (but may extend beyond the actual font run into the bidi wrapping text).
     //     aStringOffset tells us how many initial characters of the line to ignore.
 
     // get the source string range within the CTLine's text
     CFRange stringRange = ::CTRunGetStringRange(aCTRun);
     // skip the run if it is entirely outside the actual range of the font run
     if (stringRange.location - aStringOffset + stringRange.length <= 0 ||
-        stringRange.location - aStringOffset >= aRunLength) {
+        stringRange.location - aStringOffset >= wordLength) {
         return NS_OK;
     }
 
     // retrieve the laid-out glyph data from the CTRun
     nsAutoArrayPtr<CGGlyph> glyphsArray;
     nsAutoArrayPtr<CGPoint> positionsArray;
     nsAutoArrayPtr<CFIndex> glyphToCharArray;
     const CGGlyph* glyphs = NULL;
@@ -312,17 +297,16 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT
         ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), glyphToCharArray.get());
         glyphToChar = glyphToCharArray.get();
     }
 
     double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0), NULL, NULL, NULL);
 
     nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs;
     gfxTextRun::CompressedGlyph g;
-    const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
 
     // CoreText gives us the glyphindex-to-charindex mapping, which relates each glyph
     // to a source text character; we also need the charindex-to-glyphindex mapping to
     // find the glyph for a given char. Note that some chars may not map to any glyph
     // (ligature continuations), and some may map to several glyphs (eg Indic split vowels).
     // We set the glyph index to NO_GLYPH for chars that have no associated glyph, and we
     // record the last glyph index for cases where the char maps to several glyphs,
     // so that our clumping will include all the glyph fragments for the character.
@@ -352,31 +336,33 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT
     // and extend it until it includes the character associated with the first glyph;
     // we also extend it as long as there are "holes" in the range of glyphs. So we
     // will eventually have a contiguous sequence of characters, starting at the beginning
     // of the range, that map to a contiguous sequence of glyphs, starting at the beginning
     // of the glyph array. That's a clump; then we update the starting positions and repeat.
     //
     // NB: In the case of RTL layouts, we iterate over the stringRange in reverse.
     //
-    // This may find characters that fall outside the range aRunStart:aRunLength,
+
+    // This may find characters that fall outside the range 0:wordLength,
     // so we won't necessarily use everything we find here.
 
+    bool isRightToLeft = aShapedWord->IsRightToLeft();
     PRInt32 glyphStart = 0; // looking for a clump that starts at this glyph index
-    PRInt32 charStart = isLTR ?
-        0 : stringRange.length-1; // and this char index (in the stringRange of the glyph run)
+    PRInt32 charStart = isRightToLeft ?
+        stringRange.length - 1 : 0; // and this char index (in the stringRange of the glyph run)
 
     while (glyphStart < numGlyphs) { // keep finding groups until all glyphs are accounted for
 
         bool inOrder = true;
         PRInt32 charEnd = glyphToChar[glyphStart] - stringRange.location;
         NS_ASSERTION(charEnd >= 0 && charEnd < stringRange.length,
                      "glyph-to-char mapping points outside string range");
         PRInt32 glyphEnd = glyphStart;
-        PRInt32 charLimit = isLTR ? stringRange.length : -1;
+        PRInt32 charLimit = isRightToLeft ? -1 : stringRange.length;
         do {
             // This is normally executed once for each iteration of the outer loop,
             // but in unusual cases where the character/glyph association is complex,
             // the initial character range might correspond to a non-contiguous
             // glyph range with "holes" in it. If so, we will repeat this loop to
             // extend the character range until we have a contiguous glyph sequence.
             NS_ASSERTION((direction > 0 && charEnd < charLimit) ||
                          (direction < 0 && charEnd > charLimit),
@@ -405,93 +391,95 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT
 
             // check whether all glyphs in the range are associated with the characters
             // in our clump; if not, we have a discontinuous range, and should extend it
             // unless we've reached the end of the text
             bool allGlyphsAreWithinCluster = true;
             PRInt32 prevGlyphCharIndex = charStart;
             for (PRInt32 i = glyphStart; i < glyphEnd; ++i) {
                 PRInt32 glyphCharIndex = glyphToChar[i] - stringRange.location;
-                if (isLTR) {
+                if (isRightToLeft) {
+                    if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) {
+                        allGlyphsAreWithinCluster = false;
+                        break;
+                    }
+                    if (glyphCharIndex > prevGlyphCharIndex) {
+                        inOrder = false;
+                    }
+                    prevGlyphCharIndex = glyphCharIndex;
+                } else {
                     if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) {
                         allGlyphsAreWithinCluster = false;
                         break;
                     }
                     if (glyphCharIndex < prevGlyphCharIndex) {
                         inOrder = false;
                     }
                     prevGlyphCharIndex = glyphCharIndex;
-                } else {
-                    if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) {
-                        allGlyphsAreWithinCluster = false;
-                        break;
-                    }
-                    if (glyphCharIndex > prevGlyphCharIndex) {
-                        inOrder = false;
-                    }
-                    prevGlyphCharIndex = glyphCharIndex;
                 }
             }
             if (allGlyphsAreWithinCluster) {
                 break;
             }
         } while (charEnd != charLimit);
 
         NS_ASSERTION(glyphStart < glyphEnd, "character/glyph clump contains no glyphs!");
         NS_ASSERTION(charStart != charEnd, "character/glyph contains no characters!");
 
         // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd;
         // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature),
         // and endCharIndex to the limit (position beyond the last char),
         // adjusting for the offset of the stringRange relative to the textRun.
         PRInt32 baseCharIndex, endCharIndex;
-        if (isLTR) {
+        if (isRightToLeft) {
+            while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) {
+                charEnd--;
+            }
+            baseCharIndex = charEnd + stringRange.location - aStringOffset + 1;
+            endCharIndex = charStart + stringRange.location - aStringOffset + 1;
+        } else {
             while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) {
                 charEnd++;
             }
-            baseCharIndex = charStart + stringRange.location - aStringOffset + aRunStart;
-            endCharIndex = charEnd + stringRange.location - aStringOffset + aRunStart;
-        } else {
-            while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) {
-                charEnd--;
-            }
-            baseCharIndex = charEnd + stringRange.location - aStringOffset + aRunStart + 1;
-            endCharIndex = charStart + stringRange.location - aStringOffset + aRunStart + 1;
+            baseCharIndex = charStart + stringRange.location - aStringOffset;
+            endCharIndex = charEnd + stringRange.location - aStringOffset;
         }
 
         // Then we check if the clump falls outside our actual string range; if so, just go to the next.
-        if (endCharIndex <= aRunStart || baseCharIndex >= aRunStart + aRunLength) {
+        if (endCharIndex <= 0 || baseCharIndex >= wordLength) {
             glyphStart = glyphEnd;
             charStart = charEnd;
             continue;
         }
-        // Ensure we won't try to go beyond the valid length of the textRun's text
-        baseCharIndex = NS_MAX(baseCharIndex, aRunStart);
-        endCharIndex = NS_MIN(endCharIndex, aRunStart + aRunLength);
+        // Ensure we won't try to go beyond the valid length of the word's text
+        baseCharIndex = NS_MAX(baseCharIndex, 0);
+        endCharIndex = NS_MIN(endCharIndex, wordLength);
 
         // Now we're ready to set the glyph info in the textRun; measure the glyph width
         // of the first (perhaps only) glyph, to see if it is "Simple"
+        PRInt32 appUnitsPerDevUnit = aShapedWord->AppUnitsPerDevUnit();
         double toNextGlyph;
         if (glyphStart < numGlyphs-1) {
             toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
         } else {
             toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
         }
         PRInt32 advance = PRInt32(toNextGlyph * appUnitsPerDevUnit);
 
         // Check if it's a simple one-to-one mapping
         PRInt32 glyphsInClump = glyphEnd - glyphStart;
         if (glyphsInClump == 1 &&
             gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) &&
             gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
-            aTextRun->IsClusterStart(baseCharIndex) &&
+            aShapedWord->IsClusterStart(baseCharIndex) &&
             positions[glyphStart].y == 0.0)
         {
-            aTextRun->SetSimpleGlyph(baseCharIndex,
-                                     g.SetSimpleGlyph(advance, glyphs[glyphStart]));
+            aShapedWord->SetSimpleGlyph(baseCharIndex,
+                                        g.SetSimpleGlyph(advance,
+                                                         glyphs[glyphStart]));
         } else {
             // collect all glyphs in a list to be assigned to the first char;
             // there must be at least one in the clump, and we already measured its advance,
             // hence the placement of the loop-exit test and the measurement of the next glyph
             while (1) {
                 gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
                 details->mGlyphID = glyphs[glyphStart];
                 details->mXOffset = 0;
@@ -504,28 +492,28 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT
                     toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
                 } else {
                     toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
                 }
                 advance = PRInt32(toNextGlyph * appUnitsPerDevUnit);
             }
 
             gfxTextRun::CompressedGlyph g;
-            g.SetComplex(aTextRun->IsClusterStart(baseCharIndex),
+            g.SetComplex(aShapedWord->IsClusterStart(baseCharIndex),
                          true, detailedGlyphs.Length());
-            aTextRun->SetGlyphs(baseCharIndex, g, detailedGlyphs.Elements());
+            aShapedWord->SetGlyphs(baseCharIndex, g, detailedGlyphs.Elements());
 
             detailedGlyphs.Clear();
         }
 
         // the rest of the chars in the group are ligature continuations, no associated glyphs
-        while (++baseCharIndex != endCharIndex && baseCharIndex < aRunStart + aRunLength) {
-            g.SetComplex(inOrder && aTextRun->IsClusterStart(baseCharIndex),
+        while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) {
+            g.SetComplex(inOrder && aShapedWord->IsClusterStart(baseCharIndex),
                          false, 0);
-            aTextRun->SetGlyphs(baseCharIndex, g, nsnull);
+            aShapedWord->SetGlyphs(baseCharIndex, g, nsnull);
         }
 
         glyphStart = glyphEnd;
         charStart = charEnd;
     }
 
     return NS_OK;
 }
--- a/gfx/thebes/gfxCoreTextShaper.h
+++ b/gfx/thebes/gfxCoreTextShaper.h
@@ -52,35 +52,30 @@
 class gfxMacFont;
 
 class gfxCoreTextShaper : public gfxFontShaper {
 public:
     gfxCoreTextShaper(gfxMacFont *aFont);
 
     virtual ~gfxCoreTextShaper();
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aText);
 
     // clean up static objects that may have been cached
     static void Shutdown();
 
 protected:
     CTFontRef mCTFont;
     CFDictionaryRef mAttributesDict;
 
-    nsresult SetGlyphsFromRun(gfxTextRun *aTextRun,
+    nsresult SetGlyphsFromRun(gfxShapedWord *aShapedWord,
                               CTRunRef aCTRun,
-                              PRInt32 aStringOffset,
-                              PRInt32 aLayoutStart,
-                              PRInt32 aLayoutLength);
+                              PRInt32 aStringOffset);
 
     CTFontRef CreateCTFontWithDisabledLigatures(CGFloat aSize);
 
     static void CreateDefaultFeaturesDescriptor();
 
     static CTFontDescriptorRef GetDefaultFeaturesDescriptor() {
         if (sDefaultFeaturesDescriptor == NULL) {
             CreateDefaultFeaturesDescriptor();
--- a/gfx/thebes/gfxDWriteShaper.cpp
+++ b/gfx/thebes/gfxDWriteShaper.cpp
@@ -39,261 +39,219 @@
 #include "gfxWindowsPlatform.h"
 
 #include <dwrite.h>
 
 #include "gfxDWriteTextAnalysis.h"
 
 #include "nsCRT.h"
 
-#define MAX_RANGE_LENGTH 25000
-
 bool
-gfxDWriteShaper::InitTextRun(gfxContext *aContext,
-                             gfxTextRun *aTextRun,
-                             const PRUnichar *aString,
-                             PRUint32 aRunStart,
-                             PRUint32 aRunLength,
-                             PRInt32 aRunScript)
+gfxDWriteShaper::ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString)
 {
     HRESULT hr;
     // TODO: Handle TEST_DISABLE_OPTIONAL_LIGATURES
 
     DWRITE_READING_DIRECTION readingDirection = 
-        aTextRun->IsRightToLeft()
+        aShapedWord->IsRightToLeft()
             ? DWRITE_READING_DIRECTION_RIGHT_TO_LEFT
             : DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
 
     gfxDWriteFont *font = static_cast<gfxDWriteFont*>(mFont);
 
-    gfxTextRun::CompressedGlyph g;
-
-    nsRefPtr<IDWriteTextAnalyzer> analyzer;
+    gfxShapedWord::CompressedGlyph g;
 
-    hr = gfxWindowsPlatform::GetPlatform()->GetDWriteFactory()->
-        CreateTextAnalyzer(getter_AddRefs(analyzer));
-    if (FAILED(hr)) {
+    IDWriteTextAnalyzer *analyzer =
+        gfxWindowsPlatform::GetPlatform()->GetDWriteAnalyzer();
+    if (!analyzer) {
         return false;
     }
 
     /**
-     * There's an internal 16-bit limit on some things inside the analyzer.
-     * to be on the safe side here we split up any ranges over MAX_RANGE_LENGTH 
-     * characters.
-     * TODO: Figure out what exactly is going on, and what is a safe number, and 
-     * why.
+     * There's an internal 16-bit limit on some things inside the analyzer,
+     * but we never attempt to shape a word longer than 64K characters
+     * in a single gfxShapedWord, so we cannot exceed that limit.
      */
-    bool result = true;
-    UINT32 rangeOffset = 0;
-    while (rangeOffset < aRunLength) {
-        PRUint32 rangeLen = NS_MIN<PRUint32>(aRunLength - rangeOffset,
-                                             MAX_RANGE_LENGTH);
-        if (rangeOffset + rangeLen < aRunLength) {
-            // Iterate backwards to find a decent place to break shaping:
-            // look for a whitespace char, and if that's not found then
-            // at least a char where IsClusterStart is true.
-            // However, we never iterate back further than half-way;
-            // if a decent break is not found by then, we just chop anyway
-            // at the original range length.
-            PRUint32 adjRangeLen = 0;
-            const PRUnichar *rangeStr = aString + aRunStart + rangeOffset;
-            for (PRUint32 i = rangeLen; i > MAX_RANGE_LENGTH / 2; i--) {
-                if (nsCRT::IsAsciiSpace(rangeStr[i])) {
-                    adjRangeLen = i;
-                    break;
-                }
-                if (adjRangeLen == 0 &&
-                    aTextRun->IsClusterStart(aRunStart + rangeOffset + i)) {
-                    adjRangeLen = i;
-                }
-            }
-            if (adjRangeLen != 0) {
-                rangeLen = adjRangeLen;
-            }
-        }
+    UINT32 length = aShapedWord->Length();
+
+    TextAnalysis analysis(aString, length, NULL, readingDirection);
+    TextAnalysis::Run *runHead;
+    hr = analysis.GenerateResults(analyzer, &runHead);
+
+    if (FAILED(hr)) {
+        NS_WARNING("Analyzer failed to generate results.");
+        return false;
+    }
+
+    PRUint32 appUnitsPerDevPixel = aShapedWord->AppUnitsPerDevUnit();
 
-        PRUint32 rangeStart = aRunStart + rangeOffset;
-        rangeOffset += rangeLen;
-        TextAnalysis analysis(aString + rangeStart, rangeLen,
-            NULL, 
-            readingDirection);
-        TextAnalysis::Run *runHead;
-        DWRITE_LINE_BREAKPOINT *linebreaks;
-        hr = analysis.GenerateResults(analyzer, &runHead, &linebreaks);
-
-        if (FAILED(hr)) {
-            NS_WARNING("Analyzer failed to generate results.");
-            result = false;
-            break;
-        }
-
-        PRUint32 appUnitsPerDevPixel = aTextRun->GetAppUnitsPerDevUnit();
-
-        UINT32 maxGlyphs = 0;
+    UINT32 maxGlyphs = 0;
 trymoreglyphs:
-        if ((PR_UINT32_MAX - 3 * rangeLen / 2 + 16) < maxGlyphs) {
-            // This isn't going to work, we're going to cross the UINT32 upper
-            // limit. Next range it is.
-            continue;
-        }
-        maxGlyphs += 3 * rangeLen / 2 + 16;
+    if ((PR_UINT32_MAX - 3 * length / 2 + 16) < maxGlyphs) {
+        // This isn't going to work, we're going to cross the UINT32 upper
+        // limit. Give up.
+        NS_WARNING("Shaper needs to generate more than 2^32 glyphs?!");
+        return false;
+    }
+    maxGlyphs += 3 * length / 2 + 16;
 
-        nsAutoTArray<UINT16, 400> clusters;
-        nsAutoTArray<UINT16, 400> indices;
-        nsAutoTArray<DWRITE_SHAPING_TEXT_PROPERTIES, 400> textProperties;
-        nsAutoTArray<DWRITE_SHAPING_GLYPH_PROPERTIES, 400> glyphProperties;
-        if (!clusters.SetLength(rangeLen) ||
-            !indices.SetLength(maxGlyphs) || 
-            !textProperties.SetLength(maxGlyphs) ||
-            !glyphProperties.SetLength(maxGlyphs)) {
-                continue;
-        }
+    nsAutoTArray<UINT16, 400> clusters;
+    nsAutoTArray<UINT16, 400> indices;
+    nsAutoTArray<DWRITE_SHAPING_TEXT_PROPERTIES, 400> textProperties;
+    nsAutoTArray<DWRITE_SHAPING_GLYPH_PROPERTIES, 400> glyphProperties;
+    if (!clusters.SetLength(length) ||
+        !indices.SetLength(maxGlyphs) || 
+        !textProperties.SetLength(maxGlyphs) ||
+        !glyphProperties.SetLength(maxGlyphs)) {
+        NS_WARNING("Shaper failed to allocate memory.");
+        return false;
+    }
 
-        UINT32 actualGlyphs;
+    UINT32 actualGlyphs;
 
-        hr = analyzer->GetGlyphs(aString + rangeStart, rangeLen,
+    hr = analyzer->GetGlyphs(aString, length,
             font->GetFontFace(), FALSE, 
             readingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT,
             &runHead->mScript, NULL, NULL, NULL, NULL, 0,
             maxGlyphs, clusters.Elements(), textProperties.Elements(),
             indices.Elements(), glyphProperties.Elements(), &actualGlyphs);
 
-        if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
-            // We increase the amount of glyphs and try again.
-            goto trymoreglyphs;
-        }
-        if (FAILED(hr)) {
-            NS_WARNING("Analyzer failed to get glyphs.");
-            result = false;
-            break;
-        }
+    if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
+        // We increase the amount of glyphs and try again.
+        goto trymoreglyphs;
+    }
+    if (FAILED(hr)) {
+        NS_WARNING("Analyzer failed to get glyphs.");
+        return false;
+    }
+
+    WORD gID = indices[0];
+    nsAutoTArray<FLOAT, 400> advances;
+    nsAutoTArray<DWRITE_GLYPH_OFFSET, 400> glyphOffsets;
+    if (!advances.SetLength(actualGlyphs) || 
+        !glyphOffsets.SetLength(actualGlyphs)) {
+        NS_WARNING("Shaper failed to allocate memory.");
+        return false;
+    }
 
-        WORD gID = indices[0];
-        nsAutoTArray<FLOAT, 400> advances;
-        nsAutoTArray<DWRITE_GLYPH_OFFSET, 400> glyphOffsets;
-        if (!advances.SetLength(actualGlyphs) || 
-            !glyphOffsets.SetLength(actualGlyphs)) {
+    if (!static_cast<gfxDWriteFont*>(mFont)->mUseSubpixelPositions) {
+        hr = analyzer->GetGdiCompatibleGlyphPlacements(
+                                          aString,
+                                          clusters.Elements(),
+                                          textProperties.Elements(),
+                                          length,
+                                          indices.Elements(),
+                                          glyphProperties.Elements(),
+                                          actualGlyphs,
+                                          font->GetFontFace(),
+                                          font->GetAdjustedSize(),
+                                          1.0,
+                                          nsnull,
+                                          FALSE,
+                                          FALSE,
+                                          FALSE,
+                                          &runHead->mScript,
+                                          NULL,
+                                          NULL,
+                                          NULL,
+                                          0,
+                                          advances.Elements(),
+                                          glyphOffsets.Elements());
+    } else {
+        hr = analyzer->GetGlyphPlacements(aString,
+                                          clusters.Elements(),
+                                          textProperties.Elements(),
+                                          length,
+                                          indices.Elements(),
+                                          glyphProperties.Elements(),
+                                          actualGlyphs,
+                                          font->GetFontFace(),
+                                          font->GetAdjustedSize(),
+                                          FALSE,
+                                          FALSE,
+                                          &runHead->mScript,
+                                          NULL,
+                                          NULL,
+                                          NULL,
+                                          0,
+                                          advances.Elements(),
+                                          glyphOffsets.Elements());
+    }
+    if (FAILED(hr)) {
+        NS_WARNING("Analyzer failed to get glyph placements.");
+        return false;
+    }
+
+    nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs;
+
+    for (unsigned int c = 0; c < length; c++) {
+        PRUint32 k = clusters[c];
+        PRUint32 absC = c;
+
+        if (c > 0 && k == clusters[c - 1]) {
+            g.SetComplex(aShapedWord->IsClusterStart(absC), false, 0);
+            aShapedWord->SetGlyphs(absC, g, nsnull);
+            // This is a cluster continuation. No glyph here.
             continue;
         }
 
-        if (!static_cast<gfxDWriteFont*>(mFont)->mUseSubpixelPositions) {
-            hr = analyzer->GetGdiCompatibleGlyphPlacements(
-                                              aString + rangeStart,
-                                              clusters.Elements(),
-                                              textProperties.Elements(),
-                                              rangeLen,
-                                              indices.Elements(),
-                                              glyphProperties.Elements(),
-                                              actualGlyphs,
-                                              font->GetFontFace(),
-                                              font->GetAdjustedSize(),
-                                              1.0,
-                                              nsnull,
-                                              FALSE,
-                                              FALSE,
-                                              FALSE,
-                                              &runHead->mScript,
-                                              NULL,
-                                              NULL,
-                                              NULL,
-                                              0,
-                                              advances.Elements(),
-                                              glyphOffsets.Elements());
+        // Count glyphs for this character
+        PRUint32 glyphCount = actualGlyphs - k;
+        PRUint32 nextClusterOffset;
+        for (nextClusterOffset = c + 1; 
+            nextClusterOffset < length; ++nextClusterOffset) {
+            if (clusters[nextClusterOffset] > k) {
+                glyphCount = clusters[nextClusterOffset] - k;
+                break;
+            }
+        }
+        PRInt32 advance = (PRInt32)(advances[k] * appUnitsPerDevPixel);
+        if (glyphCount == 1 && advance >= 0 &&
+            glyphOffsets[k].advanceOffset == 0 &&
+            glyphOffsets[k].ascenderOffset == 0 &&
+            aShapedWord->IsClusterStart(absC) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advance) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(indices[k])) {
+              aShapedWord->SetSimpleGlyph(absC, 
+                                          g.SetSimpleGlyph(advance, 
+                                                           indices[k]));
         } else {
-            hr = analyzer->GetGlyphPlacements(aString + rangeStart,
-                                              clusters.Elements(),
-                                              textProperties.Elements(),
-                                              rangeLen,
-                                              indices.Elements(),
-                                              glyphProperties.Elements(),
-                                              actualGlyphs,
-                                              font->GetFontFace(),
-                                              font->GetAdjustedSize(),
-                                              FALSE,
-                                              FALSE,
-                                              &runHead->mScript,
-                                              NULL,
-                                              NULL,
-                                              NULL,
-                                              0,
-                                              advances.Elements(),
-                                              glyphOffsets.Elements());
-        }
-        if (FAILED(hr)) {
-            NS_WARNING("Analyzer failed to get glyph placements.");
-            result = false;
-            break;
-        }
-
-        nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs;
-
-        for (unsigned int c = 0; c < rangeLen; c++) {
-            PRUint32 k = clusters[c];
-            PRUint32 absC = rangeStart + c;
-
-            if (c > 0 && k == clusters[c - 1]) {
-                g.SetComplex(aTextRun->IsClusterStart(absC), false, 0);
-                aTextRun->SetGlyphs(absC, g, nsnull);
-                // This is a cluster continuation. No glyph here.
-                continue;
-            }
-
-            // Count glyphs for this character
-            PRUint32 glyphCount = actualGlyphs - k;
-            PRUint32 nextClusterOffset;
-            for (nextClusterOffset = c + 1; 
-                nextClusterOffset < rangeLen; ++nextClusterOffset) {
-                if (clusters[nextClusterOffset] > k) {
-                    glyphCount = clusters[nextClusterOffset] - k;
-                    break;
+            if (detailedGlyphs.Length() < glyphCount) {
+                if (!detailedGlyphs.AppendElements(
+                    glyphCount - detailedGlyphs.Length())) {
+                    continue;
                 }
             }
-            PRInt32 advance = (PRInt32)(advances[k] * appUnitsPerDevPixel);
-            if (glyphCount == 1 && advance >= 0 &&
-                glyphOffsets[k].advanceOffset == 0 &&
-                glyphOffsets[k].ascenderOffset == 0 &&
-                aTextRun->IsClusterStart(absC) &&
-                gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
-                gfxTextRun::CompressedGlyph::IsSimpleGlyphID(indices[k])) {
-                  aTextRun->SetSimpleGlyph(absC, 
-                                          g.SetSimpleGlyph(advance, 
-                                                           indices[k]));
-            } else {
-                if (detailedGlyphs.Length() < glyphCount) {
-                    if (!detailedGlyphs.AppendElements(
-                        glyphCount - detailedGlyphs.Length())) {
-                        continue;
-                    }
+            float totalAdvance = 0;
+            for (unsigned int z = 0; z < glyphCount; z++) {
+                detailedGlyphs[z].mGlyphID = indices[k + z];
+                detailedGlyphs[z].mAdvance = 
+                    (PRInt32)(advances[k + z]
+                       * appUnitsPerDevPixel);
+                if (readingDirection == 
+                    DWRITE_READING_DIRECTION_RIGHT_TO_LEFT) {
+                    detailedGlyphs[z].mXOffset = 
+                        (totalAdvance + 
+                          glyphOffsets[k + z].advanceOffset)
+                        * appUnitsPerDevPixel;
+                } else {
+                    detailedGlyphs[z].mXOffset = 
+                        glyphOffsets[k + z].advanceOffset *
+                        appUnitsPerDevPixel;
                 }
-                float totalAdvance = 0;
-                for (unsigned int z = 0; z < glyphCount; z++) {
-                    detailedGlyphs[z].mGlyphID = indices[k + z];
-                    detailedGlyphs[z].mAdvance = 
-                        (PRInt32)(advances[k + z]
-                           * appUnitsPerDevPixel);
-                    if (readingDirection == 
-                        DWRITE_READING_DIRECTION_RIGHT_TO_LEFT) {
-                        detailedGlyphs[z].mXOffset = 
-                            (totalAdvance + 
-                              glyphOffsets[k + z].advanceOffset)
-                            * appUnitsPerDevPixel;
-                    } else {
-                        detailedGlyphs[z].mXOffset = 
-                            glyphOffsets[k + z].advanceOffset *
-                            appUnitsPerDevPixel;
-                    }
-                    detailedGlyphs[z].mYOffset = 
-                        -glyphOffsets[k + z].ascenderOffset *
-                        appUnitsPerDevPixel;
-                    totalAdvance += advances[k + z];
-                }
-                aTextRun->SetGlyphs(
-                    absC,
-                    g.SetComplex(aTextRun->IsClusterStart(absC),
-                                 true,
-                                 glyphCount),
-                    detailedGlyphs.Elements());
+                detailedGlyphs[z].mYOffset = 
+                    -glyphOffsets[k + z].ascenderOffset *
+                    appUnitsPerDevPixel;
+                totalAdvance += advances[k + z];
             }
+            aShapedWord->SetGlyphs(
+                absC,
+                g.SetComplex(aShapedWord->IsClusterStart(absC),
+                             true,
+                             glyphCount),
+                detailedGlyphs.Elements());
         }
     }
 
-    return result;
+    return true;
 }
--- a/gfx/thebes/gfxDWriteShaper.h
+++ b/gfx/thebes/gfxDWriteShaper.h
@@ -52,17 +52,14 @@ public:
         MOZ_COUNT_CTOR(gfxDWriteShaper);
     }
 
     virtual ~gfxDWriteShaper()
     {
         MOZ_COUNT_DTOR(gfxDWriteShaper);
     }
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString);
 };
 
 #endif /* GFX_DWRITESHAPER_H */
--- a/gfx/thebes/gfxDWriteTextAnalysis.cpp
+++ b/gfx/thebes/gfxDWriteTextAnalysis.cpp
@@ -40,80 +40,54 @@
 TextAnalysis::TextAnalysis(const wchar_t* text,
                            UINT32 textLength,
                            const wchar_t* localeName,
                            DWRITE_READING_DIRECTION readingDirection)
   : mText(text)
   , mTextLength(textLength)
   , mLocaleName(localeName)
   , mReadingDirection(readingDirection)
-  , mBreakpoints(NULL)
-  , mRunHead(NULL)
   , mCurrentRun(NULL)
 {
 }
 
 TextAnalysis::~TextAnalysis()
 {
-    delete [] mBreakpoints;
-    for (Run *run = mRunHead; run;) {
+    // delete runs, except mRunHead which is part of the TextAnalysis object
+    for (Run *run = mRunHead.nextRun; run;) {
         Run *origRun = run;
         run = run->nextRun;
         delete origRun;
     }
 }
 
 STDMETHODIMP 
 TextAnalysis::GenerateResults(IDWriteTextAnalyzer* textAnalyzer,
-                              OUT Run **runHead,
-                              OUT DWRITE_LINE_BREAKPOINT **breakpoints)
+                              OUT Run **runHead)
 {
-    // Analyzes the text using each of the analyzers and returns
-    // their results as a series of runs.
+    // Analyzes the text using the script analyzer and returns
+    // the result as a series of runs.
 
     HRESULT hr = S_OK;
 
     // Initially start out with one result that covers the entire range.
     // This result will be subdivided by the analysis processes.
-    mRunHead = new Run;
-    
-    mRunHead->mTextStart = 0;
-    mRunHead->mTextLength = mTextLength;
-    mRunHead->mBidiLevel = 
+    mRunHead.mTextStart = 0;
+    mRunHead.mTextLength = mTextLength;
+    mRunHead.mBidiLevel = 
         (mReadingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);
-    mRunHead->nextRun = NULL;
-    mCurrentRun = mRunHead;
-#ifdef USE_DWRITE_BREAKPOINTS
-    delete [] mBreakpoints;
-    mBreakpoints = new DWRITE_LINE_BREAKPOINT[mTextLength];
-#endif
+    mRunHead.nextRun = NULL;
+    mCurrentRun = &mRunHead;
 
     // Call each of the analyzers in sequence, recording their results.
-    if (
-#ifdef USE_DWRITE_BREAKPOINTS
-        SUCCEEDED(hr = textAnalyzer->AnalyzeLineBreakpoints(this,
-                                                            0,
-                                                            mTextLength,
-                                                            this)) && 
-#endif
-        SUCCEEDED(hr = textAnalyzer->AnalyzeBidi(this,
-                                                 0,
-                                                 mTextLength,
-                                                 this)) &&
-        SUCCEEDED(hr = textAnalyzer->AnalyzeScript(this,
+    if (SUCCEEDED(hr = textAnalyzer->AnalyzeScript(this,
                                                    0,
                                                    mTextLength,
-                                                   this)) &&
-        SUCCEEDED(hr = textAnalyzer->AnalyzeNumberSubstitution(this,
-                                                               0,
-                                                               mTextLength,
-                                                               this))) {
-        *breakpoints = mBreakpoints;
-
-        *runHead = mRunHead;
+                                                   this))) {
+        *runHead = &mRunHead;
     }
 
     return hr;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // IDWriteTextAnalysisSource source implementation
@@ -190,21 +164,17 @@ TextAnalysis::GetNumberSubstitution(UINT
 ////////////////////////////////////////////////////////////////////////////////
 // IDWriteTextAnalysisSink implementation
 
 IFACEMETHODIMP 
 TextAnalysis::SetLineBreakpoints(UINT32 textPosition,
                                  UINT32 textLength,
                                  DWRITE_LINE_BREAKPOINT const* lineBreakpoints)
 {
-    if (textLength > 0) {
-        memcpy(mBreakpoints + textPosition,
-               lineBreakpoints,
-               textLength * sizeof(DWRITE_LINE_BREAKPOINT));
-    }
+    // We don't use this for now.
     return S_OK;
 }
 
 
 IFACEMETHODIMP 
 TextAnalysis::SetScriptAnalysis(UINT32 textPosition,
                                 UINT32 textLength,
                                 DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis)
@@ -221,23 +191,17 @@ TextAnalysis::SetScriptAnalysis(UINT32 t
 
 
 IFACEMETHODIMP 
 TextAnalysis::SetBidiLevel(UINT32 textPosition,
                            UINT32 textLength,
                            UINT8 explicitLevel,
                            UINT8 resolvedLevel)
 {
-    SetCurrentRun(textPosition);
-    SplitCurrentRun(textPosition);
-    while (textLength > 0) {
-        Run *run = FetchNextRun(&textLength);
-        run->mBidiLevel = resolvedLevel;
-    }
-
+    // We don't use this for now.
     return S_OK;
 }
 
 
 IFACEMETHODIMP 
 TextAnalysis::SetNumberSubstitution(UINT32 textPosition,
                                     UINT32 textLength,
                                     IDWriteNumberSubstitution* numberSubstitution)
@@ -279,17 +243,17 @@ void TextAnalysis::SetCurrentRun(UINT32 
     // Since the analyzers generally return results in a forward manner,
     // this will usually just return early. If not, find the
     // corresponding run for the text position.
 
     if (mCurrentRun && mCurrentRun->ContainsTextPosition(textPosition)) {
         return;
     }
 
-    for (Run *run = mRunHead; run; run = run->nextRun) {
+    for (Run *run = &mRunHead; run; run = run->nextRun) {
         if (run->ContainsTextPosition(textPosition)) {
             mCurrentRun = run;
             return;
         }
     }
     NS_NOTREACHED("We should always be able to find the text position in one \
         of our runs");
 }
--- a/gfx/thebes/gfxDWriteTextAnalysis.h
+++ b/gfx/thebes/gfxDWriteTextAnalysis.h
@@ -101,18 +101,17 @@ public:
     TextAnalysis(const wchar_t* text,
                  UINT32 textLength,
                  const wchar_t* localeName,
                  DWRITE_READING_DIRECTION readingDirection);
 
     ~TextAnalysis();
 
     STDMETHODIMP GenerateResults(IDWriteTextAnalyzer* textAnalyzer,
-                                 Run **runHead,
-                                 DWRITE_LINE_BREAKPOINT **breakpoints);
+                                 Run **runHead);
 
     // IDWriteTextAnalysisSource implementation
 
     IFACEMETHODIMP GetTextAtPosition(UINT32 textPosition,
                                      OUT WCHAR const** textString,
                                      OUT UINT32* textLength);
 
     IFACEMETHODIMP GetTextBeforePosition(UINT32 textPosition,
@@ -167,16 +166,13 @@ protected:
     UINT32 mTextLength;
     const wchar_t* mText;
     const wchar_t* mLocaleName;
     DWRITE_READING_DIRECTION mReadingDirection;
 
     // Current processing state.
     Run *mCurrentRun;
 
-    // Output
-    Run *mRunHead;
-
-    // We do not use this for now, store anyway
-    DWRITE_LINE_BREAKPOINT *mBreakpoints;
+    // Output is a list of runs starting here
+    Run  mRunHead;
 };
 
 #endif /* GFX_DWRITETEXTANALYSIS_H */
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -420,79 +420,78 @@ gfxFT2FontGroup::WhichSystemFontSupports
 
 #endif // !ANDROID
 
 /**
  * gfxFT2Font
  */
 
 bool
-gfxFT2Font::InitTextRun(gfxContext *aContext,
-                        gfxTextRun *aTextRun,
-                        const PRUnichar *aString,
-                        PRUint32 aRunStart,
-                        PRUint32 aRunLength,
-                        PRInt32 aRunScript,
-                        bool aPreferPlatformShaping)
+gfxFT2Font::ShapeWord(gfxContext *aContext,
+                      gfxShapedWord *aShapedWord,
+                      const PRUnichar *aString,
+                      bool aPreferPlatformShaping)
 {
     bool ok = false;
 
 #ifdef MOZ_GRAPHITE
     if (FontCanSupportGraphite()) {
         if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
             if (!mGraphiteShaper) {
                 mGraphiteShaper = new gfxGraphiteShaper(this);
             }
-            ok = mGraphiteShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength,
-                                              aRunScript);
+            ok = mGraphiteShaper->ShapeWord(aContext, aShapedWord, aString);
         }
     }
 #endif
 
-    if (!ok && gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aRunScript)) {
+    if (!ok && gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aShapedWord->Script())) {
         if (!mHarfBuzzShaper) {
             gfxFT2LockedFace face(this);
             mFUnitsConvFactor = face.XScale();
 
             mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
         }
-        ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString,
-                                          aRunStart, aRunLength, aRunScript);
+        ok = mHarfBuzzShaper->ShapeWord(aContext, aShapedWord, aString);
     }
 
     if (!ok) {
-        AddRange(aTextRun, aString, aRunStart, aRunLength);
+        AddRange(aShapedWord, aString);
     }
 
-    aTextRun->AdjustAdvancesForSyntheticBold(aContext, aRunStart, aRunLength);
+    if (IsSyntheticBold()) {
+        float synBoldOffset =
+            GetSyntheticBoldOffset() * CalcXScale(aContext);
+        aShapedWord->AdjustAdvancesForSyntheticBold(synBoldOffset);
+    }
 
     return true;
 }
 
 void
-gfxFT2Font::AddRange(gfxTextRun *aTextRun, const PRUnichar *str, PRUint32 offset, PRUint32 len)
+gfxFT2Font::AddRange(gfxShapedWord *aShapedWord, const PRUnichar *str)
 {
-    const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+    const PRUint32 appUnitsPerDevUnit = aShapedWord->AppUnitsPerDevUnit();
     // we'll pass this in/figure it out dynamically, but at this point there can be only one face.
     gfxFT2LockedFace faceLock(this);
     FT_Face face = faceLock.get();
 
-    gfxTextRun::CompressedGlyph g;
+    gfxShapedWord::CompressedGlyph g;
 
     const gfxFT2Font::CachedGlyphData *cgd = nsnull, *cgdNext = nsnull;
 
     FT_UInt spaceGlyph = GetSpaceGlyph();
 
+    PRUint32 len = aShapedWord->Length();
     for (PRUint32 i = 0; i < len; i++) {
-        PRUint32 ch = str[offset + i];
+        PRUnichar ch = str[i];
 
         if (ch == 0) {
             // treat this null byte as a missing glyph, don't create a glyph for it
-            aTextRun->SetMissingGlyph(offset + i, 0);
+            aShapedWord->SetMissingGlyph(i, 0, this);
             continue;
         }
 
         NS_ASSERTION(!gfxFontGroup::IsInvalidChar(ch), "Invalid char detected");
 
         if (cgdNext) {
             cgd = cgdNext;
             cgdNext = nsnull;
@@ -503,22 +502,22 @@ gfxFT2Font::AddRange(gfxTextRun *aTextRu
         FT_UInt gid = cgd->glyphIndex;
         PRInt32 advance = 0;
 
         if (gid == 0) {
             advance = -1; // trigger the missing glyphs case below
         } else {
             // find next character and its glyph -- in case they exist
             // and exist in the current font face -- to compute kerning
-            PRUint32 chNext = 0;
+            PRUnichar chNext = 0;
             FT_UInt gidNext = 0;
             FT_Pos lsbDeltaNext = 0;
 
             if (FT_HAS_KERNING(face) && i + 1 < len) {
-                chNext = str[offset + i + 1];
+                chNext = str[i + 1];
                 if (chNext != 0) {
                     cgdNext = GetGlyphDataForChar(chNext);
                     gidNext = cgdNext->glyphIndex;
                     if (gidNext && gidNext != spaceGlyph)
                         lsbDeltaNext = cgdNext->lsbDelta;
                 }
             }
 
@@ -536,37 +535,33 @@ gfxFT2Font::AddRange(gfxTextRun *aTextRu
                 }
             }
 
             // convert 26.6 fixed point to app units
             // round rather than truncate to nearest pixel
             // because these advances are often scaled
             advance = ((advance * appUnitsPerDevUnit + 32) >> 6);
         }
-#ifdef DEBUG_thebes_2
-        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->SetSimpleGlyph(offset + i, g.SetSimpleGlyph(advance, gid));
+            gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advance) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(gid)) {
+            aShapedWord->SetSimpleGlyph(i, g.SetSimpleGlyph(advance, gid));
         } else if (gid == 0) {
             // gid = 0 only happens when the glyph is missing from the font
-            aTextRun->SetMissingGlyph(offset + i, ch);
+            aShapedWord->SetMissingGlyph(i, ch, this);
         } else {
             gfxTextRun::DetailedGlyph details;
             details.mGlyphID = gid;
             NS_ASSERTION(details.mGlyphID == gid, "Seriously weird glyph ID detected!");
             details.mAdvance = advance;
             details.mXOffset = 0;
             details.mYOffset = 0;
-            g.SetComplex(aTextRun->IsClusterStart(offset + i), true, 1);
-            aTextRun->SetGlyphs(offset + i, g, &details);
+            g.SetComplex(aShapedWord->IsClusterStart(i), true, 1);
+            aShapedWord->SetGlyphs(i, g, &details);
         }
     }
 }
 
 gfxFT2Font::gfxFT2Font(cairo_scaled_font_t *aCairoFont,
                        FT2FontEntry *aFontEntry,
                        const gfxFontStyle *aFontStyle,
                        bool aNeedsBold)
--- a/gfx/thebes/gfxFT2Fonts.h
+++ b/gfx/thebes/gfxFT2Fonts.h
@@ -92,27 +92,24 @@ public: // new functions
             // this is a new entry, fill it
             FillGlyphDataForChar(ch, &entry->mData);
         }
 
         return &entry->mData;
     }
 
 protected:
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript,
-                               bool aPreferPlatformShaping = false);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString,
+                           bool aPreferPlatformShaping = false);
 
     void FillGlyphDataForChar(PRUint32 ch, CachedGlyphData *gd);
 
-    void AddRange(gfxTextRun *aTextRun, const PRUnichar *str, PRUint32 offset, PRUint32 len);
+    void AddRange(gfxShapedWord *aShapedWord, const PRUnichar *str);
 
     typedef nsBaseHashtableET<nsUint32HashKey, CachedGlyphData> CharGlyphMapEntryType;
     typedef nsTHashtable<CharGlyphMapEntryType> CharGlyphMap;
     CharGlyphMap mCharGlyphCache;
 };
 
 #ifndef ANDROID // not needed on Android, uses the standard gfxFontGroup directly
 class THEBES_API gfxFT2FontGroup : public gfxFontGroup {
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -43,16 +43,17 @@
 #endif
 #include "prlog.h"
 
 #include "nsServiceManagerUtils.h"
 #include "nsReadableUtils.h"
 #include "nsExpirationTracker.h"
 #include "nsILanguageAtomService.h"
 #include "nsIMemoryReporter.h"
+#include "nsITimer.h"
 
 #include "gfxFont.h"
 #include "gfxPlatform.h"
 #include "gfxAtoms.h"
 
 #include "prtypes.h"
 #include "gfxTypes.h"
 #include "nsAlgorithm.h"
@@ -976,16 +977,46 @@ gfxFontCache::Shutdown()
     printf("Total glyph extents width-storage size allocated=%d\n", gGlyphExtentsWidthsTotalSize);
     printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple);
     printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight);
     printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight);
     printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight);
 #endif
 }
 
+gfxFontCache::gfxFontCache()
+    : nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000)
+{
+    mFonts.Init();
+    mWordCacheExpirationTimer = do_CreateInstance("@mozilla.org/timer;1");
+    if (mWordCacheExpirationTimer) {
+        mWordCacheExpirationTimer->
+            InitWithFuncCallback(WordCacheExpirationTimerCallback, this,
+                                 SHAPED_WORD_TIMEOUT_SECONDS * 1000,
+                                 nsITimer::TYPE_REPEATING_SLACK);
+    }
+}
+
+gfxFontCache::~gfxFontCache()
+{
+    if (mWordCacheExpirationTimer) {
+        mWordCacheExpirationTimer->Cancel();
+        mWordCacheExpirationTimer = nsnull;
+    }
+
+    // Expire everything that has a zero refcount, so we don't leak them.
+    AgeAllGenerations();
+    // All fonts should be gone.
+    NS_WARN_IF_FALSE(mFonts.Count() == 0,
+                     "Fonts still alive while shutting down gfxFontCache");
+    // Note that we have to delete everything through the expiration
+    // tracker, since there might be fonts not in the hashtable but in
+    // the tracker.
+}
+
 bool
 gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const
 {
     return aKey->mFontEntry == mFont->GetFontEntry() &&
            aKey->mStyle->Equals(*mFont->GetStyle());
 }
 
 already_AddRefed<gfxFont>
@@ -1050,16 +1081,32 @@ gfxFontCache::DestroyFont(gfxFont *aFont
     HashEntry *entry = mFonts.GetEntry(key);
     if (entry && entry->mFont == aFont)
         mFonts.RemoveEntry(key);
     NS_ASSERTION(aFont->GetRefCount() == 0,
                  "Destroying with non-zero ref count!");
     delete aFont;
 }
 
+/*static*/
+PLDHashOperator
+gfxFontCache::AgeCachedWordsForFont(HashEntry* aHashEntry, void* aUserData)
+{
+    aHashEntry->mFont->AgeCachedWords();
+    return PL_DHASH_NEXT;
+}
+
+/*static*/
+void
+gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache)
+{
+    gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
+    cache->mFonts.EnumerateEntries(AgeCachedWordsForFont, nsnull);
+}
+
 void
 gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft)
 {
     mAscent = NS_MAX(mAscent, aOther.mAscent);
     mDescent = NS_MAX(mDescent, aOther.mDescent);
     if (aOtherIsOnLeft) {
         mBoundingBox =
             (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)).Union(aOther.mBoundingBox);
@@ -1091,16 +1138,30 @@ gfxFont::~gfxFont()
     // We destroy the contents of mGlyphExtentsArray explicitly instead of
     // using nsAutoPtr because VC++ can't deal with nsTArrays of nsAutoPtrs
     // of classes that lack a proper copy constructor
     for (i = 0; i < mGlyphExtentsArray.Length(); ++i) {
         delete mGlyphExtentsArray[i];
     }
 }
 
+/*static*/
+PLDHashOperator
+gfxFont::AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData)
+{
+    if (!aEntry->mShapedWord) {
+        NS_ASSERTION(aEntry->mShapedWord, "cache entry has no gfxShapedWord!");
+        return PL_DHASH_REMOVE;
+    }
+    if (aEntry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) {
+        return PL_DHASH_REMOVE;
+    }
+    return PL_DHASH_NEXT;
+}
+
 hb_blob_t *
 gfxFont::GetFontTable(PRUint32 aTag) {
     hb_blob_t *blob;
     if (mFontEntry->GetExistingFontTable(aTag, &blob))
         return blob;
 
     FallibleTArray<PRUint8> buffer;
     bool haveTable = NS_SUCCEEDED(mFontEntry->GetFontTable(aTag, buffer));
@@ -1192,18 +1253,18 @@ struct GlyphBufferAzure {
 };
 
 // Bug 674909. When synthetic bolding text by drawing twice, need to
 // render using a pixel offset in device pixels, otherwise text
 // doesn't appear bolded, it appears as if a bad text shadow exists
 // when a non-identity transform exists.  Use an offset factor so that
 // the second draw occurs at a constant offset in device pixels.
 
-static double
-CalcXScale(gfxContext *aContext)
+double
+gfxFont::CalcXScale(gfxContext *aContext)
 {
     // determine magnitude of a 1px x offset in device space
     gfxSize t = aContext->UserToDevice(gfxSize(1.0, 0.0));
     if (t.width == 1.0 && t.height == 0.0) {
         // short-circuit the most common case to avoid sqrt() and division
         return 1.0;
     }
 
@@ -1723,144 +1784,325 @@ gfxFont::Measure(gfxTextRun *aTextRun,
 #define MAX_SHAPING_LENGTH  32760 // slightly less than 32K, trying to avoid
                                   // over-stressing platform shapers
 
 #define BACKTRACK_LIMIT  1024 // If we can't find a space or a cluster start
                               // within 1K chars, just chop arbitrarily.
                               // Limiting backtrack here avoids pathological
                               // behavior on long runs with no whitespace.
 
-bool
-gfxFont::SplitAndInitTextRun(gfxContext *aContext,
-                             gfxTextRun *aTextRun,
-                             const PRUnichar *aString,
-                             PRUint32 aRunStart,
-                             PRUint32 aRunLength,
-                             PRInt32 aRunScript)
+static bool
+IsClusterExtender(PRUint32 aUSV)
+{
+    PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(aUSV);
+    return ((category >= HB_CATEGORY_COMBINING_MARK &&
+             category <= HB_CATEGORY_NON_SPACING_MARK) ||
+            (aUSV >= 0x200c && aUSV <= 0x200d) || // ZWJ, ZWNJ
+            (aUSV >= 0xff9e && aUSV <= 0xff9f));  // katakana sound marks
+}
+
+static bool
+IsBoundarySpace(PRUnichar aChar, PRUnichar aNextChar)
+{
+    return (aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar);
+}
+
+static inline PRUint32
+HashMix(PRUint32 aHash, PRUnichar aCh)
 {
+    return (aHash >> 28) ^ (aHash << 4) ^ aCh;
+}
+
+template<typename T>
+gfxShapedWord*
+gfxFont::GetShapedWord(gfxContext *aContext,
+                       const T *aText,
+                       PRUint32 aLength,
+                       PRUint32 aHash,
+                       PRInt32 aRunScript,
+                       PRInt32 aAppUnitsPerDevUnit,
+                       PRUint32 aFlags)
+{
+    // if there's a cached entry for this word, just return it
+    CacheHashKey key(aText, aLength, aHash,
+                     aRunScript,
+                     aAppUnitsPerDevUnit,
+                     aFlags);
+
+    CacheHashEntry *entry = mWordCache.PutEntry(key);
+    gfxShapedWord *sw = entry->mShapedWord;
+    if (sw) {
+        sw->ResetAge();
+        return sw;
+    }
+
+    sw = entry->mShapedWord = gfxShapedWord::Create(aText, aLength,
+                                                    aRunScript,
+                                                    aAppUnitsPerDevUnit,
+                                                    aFlags);
+    NS_ASSERTION(sw != nsnull,
+                 "failed to create gfxShapedWord - expect missing text");
+    if (!sw) {
+        return nsnull;
+    }
+
     bool ok;
-
-#ifdef PR_LOGGING
-    PRLogModuleInfo *log = (mStyle.systemFont ?
-                            gfxPlatform::GetLog(eGfxLog_textrunui) :
-                            gfxPlatform::GetLog(eGfxLog_textrun));
-
-    if (NS_UNLIKELY(log)) {
-        nsCAutoString lang;
-        mStyle.language->ToUTF8String(lang);
-        PR_LOG(log, PR_LOG_DEBUG,\
-               ("(%s-fontmatching) font: [%s] lang: %s script: %d len: %d "
-                "TEXTRUN [%s] ENDTEXTRUN\n",
-                (mStyle.systemFont ? "textrunui" : "textrun"),
-                NS_ConvertUTF16toUTF8(GetName()).get(),
-                lang.get(), aRunScript, aRunLength,
-                NS_ConvertUTF16toUTF8(aString + aRunStart, aRunLength).get()));
-    }
-#endif
-
-    do {
-        // Because various shaping backends struggle with very long runs,
-        // we look for appropriate break locations (preferring whitespace),
-        // and shape sub-runs of no more than 32K characters at a time.
-        // See bug 606714 (CoreText), and similar Uniscribe issues.
-        // This loop always executes at least once, and "processes" up to
-        // MAX_RUN_LENGTH_FOR_SHAPING characters, updating aRunStart and
-        // aRunLength accordingly. It terminates when the entire run has
-        // been processed, or when shaping fails.
-
-        PRUint32 thisRunLength;
-        ok = false;
-
-        if (aRunLength <= MAX_SHAPING_LENGTH) {
-            thisRunLength = aRunLength;
-        } else {
-            // We're splitting this font run because it's very long
-            PRUint32 offset = aRunStart + MAX_SHAPING_LENGTH;
-            PRUint32 clusterStart = 0;
-            while (offset > aRunStart + MAX_SHAPING_LENGTH - BACKTRACK_LIMIT) {
-                if (aTextRun->IsClusterStart(offset)) {
-                    if (!clusterStart) {
-                        clusterStart = offset;
-                    }
-                    if (aString[offset] == ' ' || aString[offset - 1] == ' ') {
-                        break;
-                    }
-                }
-                --offset;
-            }
-            
-            if (offset > MAX_SHAPING_LENGTH - BACKTRACK_LIMIT) {
-                // we found a space, so break the run there
-                thisRunLength = offset - aRunStart;
-            } else if (clusterStart != 0) {
-                // didn't find a space, but we found a cluster start
-                thisRunLength = clusterStart - aRunStart;
-            } else {
-                // otherwise we'll simply break at MAX_SHAPING_LENGTH chars,
-                // which may interfere with shaping behavior (but in practice
-                // only pathological cases will lack ANY whitespace or cluster
-                // boundaries, so we don't really care; it won't affect any
-                // "real" text)
-                thisRunLength = MAX_SHAPING_LENGTH;
-            }
+    if (sizeof(T) == sizeof(PRUnichar)) {
+        ok = ShapeWord(aContext, sw, (const PRUnichar*)aText);
+    } else {
+        nsAutoString utf16;
+        AppendASCIItoUTF16(nsDependentCSubstring((const char*)aText, aLength),
+                           utf16);
+        ok = ShapeWord(aContext, sw, utf16.BeginReading());
+    }
+    NS_WARN_IF_FALSE(ok, "failed to shape word - expect garbled text");
+
+    for (PRUint32 i = 0; i < aLength; ++i) {
+        if (aText[i] == ' ') {
+            sw->SetIsSpace(i);
+        } else if (i > 0 &&
+                   NS_IS_HIGH_SURROGATE(aText[i - 1]) &&
+                   NS_IS_LOW_SURROGATE(aText[i])) {
+            sw->SetIsLowSurrogate(i);
         }
-
-        ok = InitTextRun(aContext, aTextRun, aString,
-                         aRunStart, thisRunLength, aRunScript);
-
-        aRunStart += thisRunLength;
-        aRunLength -= thisRunLength;
-    } while (ok && aRunLength > 0);
-
-    NS_WARN_IF_FALSE(ok, "shaper failed, expect scrambled or missing text");
-    return ok;
+    }
+
+    return sw;
 }
 
 bool
-gfxFont::InitTextRun(gfxContext *aContext,
-                     gfxTextRun *aTextRun,
-                     const PRUnichar *aString,
-                     PRUint32 aRunStart,
-                     PRUint32 aRunLength,
-                     PRInt32 aRunScript,
-                     bool aPreferPlatformShaping)
+gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
+{
+    const gfxShapedWord *sw = mShapedWord;
+    if (!sw) {
+        return false;
+    }
+    if (sw->Length() != aKey->mLength ||
+        sw->Flags() != aKey->mFlags ||
+        sw->AppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit ||
+        sw->Script() != aKey->mScript) {
+        return false;
+    }
+    if (sw->TextIs8Bit()) {
+        if (aKey->mTextIs8Bit) {
+            return (0 == memcmp(sw->Text8Bit(), aKey->mText.mSingle,
+                                aKey->mLength * sizeof(PRUint8)));
+        }
+        // The key has 16-bit text, even though all the characters are < 256,
+        // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
+        // comparing with will have 8-bit text.
+        const PRUint8   *s1 = sw->Text8Bit();
+        const PRUnichar *s2 = aKey->mText.mDouble;
+        const PRUnichar *s2end = s2 + aKey->mLength;
+        while (s2 < s2end) {
+            if (*s1++ != *s2++) {
+                return false;
+            }
+        }
+        return true;
+    }
+    NS_ASSERTION((aKey->mFlags & gfxTextRunFactory::TEXT_IS_8BIT) == 0 &&
+                 !aKey->mTextIs8Bit, "didn't expect 8-bit text here");
+    return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble,
+                        aKey->mLength * sizeof(PRUnichar)));
+}
+
+bool
+gfxFont::ShapeWord(gfxContext *aContext,
+                   gfxShapedWord *aShapedWord,
+                   const PRUnichar *aText,
+                   bool aPreferPlatformShaping)
 {
     bool ok = false;
 
 #ifdef MOZ_GRAPHITE
     if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
-        ok = mGraphiteShaper->InitTextRun(aContext, aTextRun, aString,
-                                          aRunStart, aRunLength,
-                                          aRunScript);
+        ok = mGraphiteShaper->ShapeWord(aContext, aShapedWord, aText);
     }
 #endif
 
     if (!ok && mHarfBuzzShaper && !aPreferPlatformShaping) {
-        if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aRunScript)) {
-            ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength,
-                                              aRunScript);
+        if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aShapedWord->Script())) {
+            ok = mHarfBuzzShaper->ShapeWord(aContext, aShapedWord, aText);
         }
     }
 
     if (!ok) {
         if (!mPlatformShaper) {
             CreatePlatformShaper();
             NS_ASSERTION(mPlatformShaper, "no platform shaper available!");
         }
         if (mPlatformShaper) {
-            ok = mPlatformShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength,
-                                              aRunScript);
+            ok = mPlatformShaper->ShapeWord(aContext, aShapedWord, aText);
         }
     }
 
+    if (ok && IsSyntheticBold()) {
+        float synBoldOffset =
+                GetSyntheticBoldOffset() * CalcXScale(aContext);
+        aShapedWord->AdjustAdvancesForSyntheticBold(synBoldOffset);
+    }
+
     return ok;
 }
 
+template<typename T>
+bool
+gfxFont::SplitAndInitTextRun(gfxContext *aContext,
+                             gfxTextRun *aTextRun,
+                             const T *aString,
+                             PRUint32 aRunStart,
+                             PRUint32 aRunLength,
+                             PRInt32 aRunScript)
+{
+    if (aRunLength == 0) {
+        return true;
+    }
+
+    InitWordCache();
+
+    // the only flags we care about for ShapedWord construction/caching
+    PRUint32 flags = aTextRun->GetFlags() &
+        (gfxTextRunFactory::TEXT_IS_RTL |
+         gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES);
+    if (sizeof(T) == sizeof(PRUint8)) {
+        flags |= gfxTextRunFactory::TEXT_IS_8BIT;
+    }
+
+    const T *text = aString + aRunStart;
+    PRUint32 wordStart = 0;
+    PRUint32 hash = 0;
+    bool wordIs8Bit = true;
+    PRInt32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+
+    T nextCh = text[0];
+    for (PRUint32 i = 0; i <= aRunLength; ++i) {
+        T ch = nextCh;
+        nextCh = (i < aRunLength - 1) ? text[i + 1] : '\n';
+        bool boundary = IsBoundarySpace(ch, nextCh);
+        bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch);
+        PRUint32 length = i - wordStart;
+
+        // break into separate ShapedWords when we hit an invalid char,
+        // or a boundary space (always handled individually),
+        // or the first non-space after a space
+        bool breakHere = boundary || invalid;
+
+        if (!breakHere) {
+            // if we're approaching the max ShapedWord length, break anyway...
+            if (sizeof(T) == sizeof(PRUint8)) {
+                // in 8-bit text, no clusters or surrogates to worry about
+                if (length >= gfxShapedWord::kMaxLength) {
+                    breakHere = true;
+                }
+            } else {
+                // try to avoid breaking before combining mark or low surrogate
+                if (length >= gfxShapedWord::kMaxLength - 15) {
+                    if (!NS_IS_LOW_SURROGATE(ch)) {
+                        if (!IsClusterExtender(ch)) {
+                            breakHere = true;
+                        }
+                    }
+                    if (!breakHere && length >= gfxShapedWord::kMaxLength - 3) {
+                        if (!NS_IS_LOW_SURROGATE(ch)) {
+                            breakHere = true;
+                        }
+                    }
+                    if (!breakHere && length >= gfxShapedWord::kMaxLength) {
+                        breakHere = true;
+                    }
+                }
+            }
+        }
+
+        if (!breakHere) {
+            if (ch >= 0x100) {
+                wordIs8Bit = false;
+            }
+            // include this character in the hash, and move on to next
+            hash = HashMix(hash, ch);
+            continue;
+        }
+
+        // We've decided to break here (i.e. we're at the end of a "word",
+        // or the word is becoming excessively long): shape the word and
+        // add it to the textrun
+        if (length > 0) {
+            PRUint32 wordFlags = flags;
+            // in the 8-bit version of this method, TEXT_IS_8BIT was
+            // already set as part of |flags|, so no need for a per-word
+            // adjustment here
+            if (sizeof(T) == sizeof(PRUnichar)) {
+                if (wordIs8Bit) {
+                    wordFlags |= gfxTextRunFactory::TEXT_IS_8BIT;
+                }
+            }
+            gfxShapedWord *sw = GetShapedWord(aContext,
+                                              text + wordStart, length,
+                                              hash, aRunScript,
+                                              appUnitsPerDevUnit,
+                                              wordFlags);
+            if (sw) {
+                aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart);
+            } else {
+                return false; // failed, presumably out of memory?
+            }
+        }
+
+        if (boundary) {
+            // word was terminated by a space: add that to the textrun
+            if (!aTextRun->SetSpaceGlyphIfSimple(this, aContext,
+                                                 aRunStart + i, ch))
+            {
+                static const PRUint8 space = ' ';
+                gfxShapedWord *sw =
+                    GetShapedWord(aContext,
+                                  &space, 1,
+                                  HashMix(0, ' '), aRunScript,
+                                  appUnitsPerDevUnit,
+                                  flags | gfxTextRunFactory::TEXT_IS_8BIT);
+                if (sw) {
+                    aTextRun->CopyGlyphDataFrom(sw, aRunStart + i);
+                } else {
+                    return false;
+                }
+            }
+            hash = 0;
+            wordStart = i + 1;
+            wordIs8Bit = true;
+            continue;
+        }
+
+        if (i == aRunLength) {
+            break;
+        }
+
+        if (invalid) {
+            // word was terminated by an invalid char: skip it,
+            // but record where TAB or NEWLINE occur
+            if (ch == '\t') {
+                aTextRun->SetIsTab(aRunStart + i);
+            } else if (ch == '\n') {
+                aTextRun->SetIsNewline(aRunStart + i);
+            }
+            hash = 0;
+            wordStart = i + 1;
+            wordIs8Bit = true;
+            continue;
+        }
+
+        // word was forcibly broken, so current char will begin next word
+        hash = HashMix(0, ch);
+        wordStart = i;
+        wordIs8Bit = (ch < 0x100);
+    }
+
+    return true;
+}
+
 gfxGlyphExtents *
 gfxFont::GetOrCreateGlyphExtents(PRUint32 aAppUnitsPerDevUnit) {
     PRUint32 i;
     for (i = 0; i < mGlyphExtentsArray.Length(); ++i) {
         if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
             return mGlyphExtentsArray[i];
     }
     gfxGlyphExtents *glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit);
@@ -2425,28 +2667,35 @@ gfxFontGroup::~gfxFontGroup() {
 
 gfxFontGroup *
 gfxFontGroup::Copy(const gfxFontStyle *aStyle)
 {
     return new gfxFontGroup(mFamilies, aStyle, mUserFontSet);
 }
 
 bool 
-gfxFontGroup::IsInvalidChar(PRUnichar ch) {
-    if (ch >= 32) {
-        return ch == 0x0085/*NEL*/ ||
-            ((ch & 0xFF00) == 0x2000 /* Unicode control character */ &&
-             (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/ ||
-              IS_BIDI_CONTROL_CHAR(ch)));
-    }
-    // We could just blacklist all control characters, but it seems better
-    // to only blacklist the ones we know cause problems for native font
-    // engines.
-    return ch == 0x0B || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\f' ||
-        (ch >= 0x1c && ch <= 0x1f);
+gfxFontGroup::IsInvalidChar(PRUint8 ch)
+{
+    return ((ch & 0x7f) < 0x20);
+}
+
+bool 
+gfxFontGroup::IsInvalidChar(PRUnichar ch)
+{
+    // All printable 7-bit ASCII values are OK
+    if (ch >= ' ' && ch < 0x80) {
+        return false;
+    }
+    // No point in sending non-printing control chars through font shaping
+    if (ch <= 0x9f) {
+        return true;
+    }
+    return ((ch & 0xFF00) == 0x2000 /* Unicode control character */ &&
+         (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/ ||
+          IS_BIDI_CONTROL_CHAR(ch)));
 }
 
 bool
 gfxFontGroup::ForEachFont(FontCreationCallback fc,
                           void *closure)
 {
     return ForEachFontInternal(mFamilies, mStyle.language,
                                true, true, true, fc, closure);
@@ -2648,214 +2897,308 @@ gfxFontGroup::MakeEmptyTextRun(const Par
 }
 
 gfxTextRun *
 gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags)
 {
     aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT;
     static const PRUint8 space = ' ';
 
-    nsAutoPtr<gfxTextRun> textRun;
-    textRun = gfxTextRun::Create(aParams, &space, 1, this, aFlags);
-    if (!textRun)
+    gfxTextRun *textRun = gfxTextRun::Create(aParams, &space, 1, this, aFlags);
+    if (!textRun) {
         return nsnull;
+    }
 
     gfxFont *font = GetFontAt(0);
     if (NS_UNLIKELY(GetStyle()->size == 0)) {
         // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
         // them, and always create at least size 1 fonts, i.e. they still
         // render something for size 0 fonts.
         textRun->AddGlyphRun(font, gfxTextRange::kFontGroup, 0, false);
     }
     else {
         textRun->SetSpaceGlyph(font, aParams->mContext, 0);
     }
+
     // Note that the gfxGlyphExtents glyph bounds storage for the font will
     // always contain an entry for the font's space glyph, so we don't have
     // to call FetchGlyphExtents here.
-    return textRun.forget();
-}
-
-#define UNICODE_LRO 0x202d
-#define UNICODE_RLO 0x202e
-#define UNICODE_PDF 0x202c
-
-inline void
-AppendDirectionalIndicatorStart(PRUint32 aFlags, nsAString& aString)
-{
-    static const PRUnichar overrides[2] = { UNICODE_LRO, UNICODE_RLO };
-    aString.Append(overrides[(aFlags & gfxTextRunFactory::TEXT_IS_RTL) != 0]);    
-    aString.Append(' ');
+    return textRun;
 }
 
-inline void
-AppendDirectionalIndicatorEnd(bool aNeedDirection, nsAString& aString)
+gfxTextRun *
+gfxFontGroup::MakeBlankTextRun(const void* aText, PRUint32 aLength,
+                               const Parameters *aParams, PRUint32 aFlags)
 {
-    // append a space (always, for consistent treatment of last char,
-    // and a direction control if required (we skip this for 8-bit text,
-    // which is known to be unidirectional LTR, unless the direction was
-    // forced RTL via overrides)
-    aString.Append(' ');
-    if (!aNeedDirection)
-        return;
-
-    aString.Append('.');
-    aString.Append(UNICODE_PDF);
+    gfxTextRun *textRun =
+        gfxTextRun::Create(aParams, aText, aLength, this, aFlags);
+    if (!textRun) {
+        return nsnull;
+    }
+
+    textRun->AddGlyphRun(GetFontAt(0), gfxTextRange::kFontGroup, 0, false);
+    return textRun;
 }
 
 gfxTextRun *
 gfxFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
                           const Parameters *aParams, PRUint32 aFlags)
 {
-    NS_ASSERTION(aLength > 0, "should use MakeEmptyTextRun for zero-length text");
-    NS_ASSERTION(aFlags & TEXT_IS_8BIT, "should be marked 8bit");
-    gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
-    if (!textRun)
+    if (aLength == 0) {
+        return MakeEmptyTextRun(aParams, aFlags);
+    }
+    if (aLength == 1 && aString[0] == ' ') {
+        return MakeSpaceTextRun(aParams, aFlags);
+    }
+
+    aFlags |= TEXT_IS_8BIT;
+
+    if (GetStyle()->size == 0) {
+        // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
+        // them, and always create at least size 1 fonts, i.e. they still
+        // render something for size 0 fonts.
+        return MakeBlankTextRun(aString, aLength, aParams, aFlags);
+    }
+
+    gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength,
+                                             this, aFlags);
+    if (!textRun) {
         return nsnull;
-
-    nsDependentCSubstring cString(reinterpret_cast<const char*>(aString),
-                                  reinterpret_cast<const char*>(aString) + aLength);
-
-    nsAutoString utf16;
-    AppendASCIItoUTF16(cString, utf16);
-
-    InitTextRun(aParams->mContext, textRun, utf16.get(), utf16.Length());
+    }
+
+    InitTextRun(aParams->mContext, textRun, aString, aLength);
 
     textRun->FetchGlyphExtents(aParams->mContext);
 
     return textRun;
 }
 
 gfxTextRun *
 gfxFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
                           const Parameters *aParams, PRUint32 aFlags)
 {
-    NS_ASSERTION(aLength > 0, "should use MakeEmptyTextRun for zero-length text");
-    gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
-    if (!textRun)
+    if (aLength == 0) {
+        return MakeEmptyTextRun(aParams, aFlags);
+    }
+    if (aLength == 1 && aString[0] == ' ') {
+        return MakeSpaceTextRun(aParams, aFlags);
+    }
+    if (GetStyle()->size == 0) {
+        return MakeBlankTextRun(aString, aLength, aParams, aFlags);
+    }
+
+    gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength,
+                                             this, aFlags);
+    if (!textRun) {
         return nsnull;
-
-    gfxPlatform::GetPlatform()->SetupClusterBoundaries(textRun, aString);
+    }
 
     InitTextRun(aParams->mContext, textRun, aString, aLength);
 
     textRun->FetchGlyphExtents(aParams->mContext);
 
     return textRun;
 }
 
+template<typename T>
 void
 gfxFontGroup::InitTextRun(gfxContext *aContext,
                           gfxTextRun *aTextRun,
-                          const PRUnichar *aString,
+                          const T *aString,
                           PRUint32 aLength)
 {
-    // split into script runs so that script can potentially influence
-    // the font matching process below
-    gfxScriptItemizer scriptRuns(aString, aLength);
-
-#ifdef PR_LOGGING
-    PRLogModuleInfo *log = (mStyle.systemFont ?
-                            gfxPlatform::GetLog(eGfxLog_textrunui) :
-                            gfxPlatform::GetLog(eGfxLog_textrun));
-#endif
-
-    PRUint32 runStart = 0, runLimit = aLength;
-    PRInt32 runScript = HB_SCRIPT_LATIN;
-    while (scriptRuns.Next(runStart, runLimit, runScript)) {
+    // we need to do numeral processing even on 8-bit text,
+    // in case we're converting Western to Hindi/Arabic digits
+    PRInt32 numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption();
+    nsAutoArrayPtr<PRUnichar> transformedString;
+    if (numOption != IBMBIDI_NUMERAL_NOMINAL) {
+        // scan the string for numerals that may need to be transformed;
+        // if we find any, we'll make a local copy here and use that for
+        // font matching and glyph generation/shaping
+        bool prevIsArabic =
+            (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0;
+        for (PRUint32 i = 0; i < aLength; ++i) {
+            PRUnichar origCh = aString[i];
+            PRUnichar newCh = HandleNumberInChar(origCh, prevIsArabic, numOption);
+            if (newCh != origCh) {
+                if (!transformedString) {
+                    transformedString = new PRUnichar[aLength];
+                    if (sizeof(T) == sizeof(PRUnichar)) {
+                        memcpy(transformedString.get(), aString, i * sizeof(PRUnichar));
+                    } else {
+                        for (PRUint32 j = 0; j < i; ++j) {
+                            transformedString[j] = aString[j];
+                        }
+                    }
+                }
+            }
+            if (transformedString) {
+                transformedString[i] = newCh;
+            }
+            prevIsArabic = IS_ARABIC_CHAR(newCh);
+        }
+    }
+
+    if (sizeof(T) == sizeof(PRUint8) && !transformedString) {
+        // the text is still purely 8-bit; bypass the script-run itemizer
+        // and treat it as a single Latin run
+        InitScriptRun(aContext, aTextRun, aString,
+                      0, aLength, HB_SCRIPT_LATIN);
+    } else {
+        const PRUnichar *textPtr;
+        if (transformedString) {
+            textPtr = transformedString.get();
+        } else {
+            // typecast to avoid compilation error for the 8-bit version,
+            // even though this is dead code in that case
+            textPtr = reinterpret_cast<const PRUnichar*>(aString);
+        }
+
+        // split into script runs so that script can potentially influence
+        // the font matching process below
+        gfxScriptItemizer scriptRuns(textPtr, aLength);
 
 #ifdef PR_LOGGING
-        if (NS_UNLIKELY(log)) {
-            nsCAutoString lang;
-            mStyle.language->ToUTF8String(lang);
-            PRUint32 runLen = runLimit - runStart;
-            PR_LOG(log, PR_LOG_DEBUG,\
-                   ("(%s) fontgroup: [%s] lang: %s script: %d len %d "
-                    "weight: %d width: %d style: %s "
-                    "TEXTRUN [%s] ENDTEXTRUN\n",
-                    (mStyle.systemFont ? "textrunui" : "textrun"),
-                    NS_ConvertUTF16toUTF8(mFamilies).get(),
-                    lang.get(), runScript, runLen,
-                    PRUint32(mStyle.weight), PRUint32(mStyle.stretch),
-                    (mStyle.style & FONT_STYLE_ITALIC ? "italic" :
-                    (mStyle.style & FONT_STYLE_OBLIQUE ? "oblique" :
-                                                            "normal")),
-                    NS_ConvertUTF16toUTF8(aString + runStart, runLen).get()));
+        PRLogModuleInfo *log = (mStyle.systemFont ?
+                                gfxPlatform::GetLog(eGfxLog_textrunui) :
+                                gfxPlatform::GetLog(eGfxLog_textrun));
+#endif
+
+        PRUint32 runStart = 0, runLimit = aLength;
+        PRInt32 runScript = HB_SCRIPT_LATIN;
+        while (scriptRuns.Next(runStart, runLimit, runScript)) {
+
+#ifdef PR_LOGGING
+            if (NS_UNLIKELY(log)) {
+                nsCAutoString lang;
+                mStyle.language->ToUTF8String(lang);
+                PRUint32 runLen = runLimit - runStart;
+                PR_LOG(log, PR_LOG_DEBUG,\
+                       ("(%s) fontgroup: [%s] lang: %s script: %d len %d "
+                        "weight: %d width: %d style: %s "
+                        "TEXTRUN [%s] ENDTEXTRUN\n",
+                        (mStyle.systemFont ? "textrunui" : "textrun"),
+                        NS_ConvertUTF16toUTF8(mFamilies).get(),
+                        lang.get(), runScript, runLen,
+                        PRUint32(mStyle.weight), PRUint32(mStyle.stretch),
+                        (mStyle.style & FONT_STYLE_ITALIC ? "italic" :
+                        (mStyle.style & FONT_STYLE_OBLIQUE ? "oblique" :
+                                                                "normal")),
+                        NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
+            }
+#endif
+
+            InitScriptRun(aContext, aTextRun, textPtr,
+                          runStart, runLimit, runScript);
         }
-#endif
-
-        InitScriptRun(aContext, aTextRun, aString, aLength,
-                      runStart, runLimit, runScript);
+    }
+
+    if (sizeof(T) == sizeof(PRUnichar) && aLength > 0) {
+        gfxTextRun::CompressedGlyph *glyph = aTextRun->GetCharacterGlyphs();
+        if (!glyph->IsSimpleGlyph()) {
+            glyph->SetClusterStart(true);
+        }
     }
 
     // It's possible for CoreText to omit glyph runs if it decides they contain
     // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we
     // need to eliminate them from the glyph run array to avoid drawing "partial
     // ligatures" with the wrong font.
     // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because
     // it will iterate back over all glyphruns in the textrun, which leads to
     // pathologically-bad perf in the case where a textrun contains many script
     // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs
     // every time a new script subrun is processed.
     aTextRun->SanitizeGlyphRuns();
 
     aTextRun->SortGlyphRuns();
 }
 
+template<typename T>
 void
 gfxFontGroup::InitScriptRun(gfxContext *aContext,
                             gfxTextRun *aTextRun,
-                            const PRUnichar *aString,
-                            PRUint32 aTotalLength,
+                            const T *aString,
                             PRUint32 aScriptRunStart,
                             PRUint32 aScriptRunEnd,
                             PRInt32 aRunScript)
 {
-    gfxFont *mainFont = mFonts[0].get();
+    gfxFont *mainFont = GetFontAt(0);
 
     PRUint32 runStart = aScriptRunStart;
     nsAutoTArray<gfxTextRange,3> fontRanges;
-    ComputeRanges(fontRanges, aString,
-                  aScriptRunStart, aScriptRunEnd, aRunScript);
+    ComputeRanges(fontRanges, aString + aScriptRunStart,
+                  aScriptRunEnd - aScriptRunStart, aRunScript);
     PRUint32 numRanges = fontRanges.Length();
 
     for (PRUint32 r = 0; r < numRanges; r++) {
         const gfxTextRange& range = fontRanges[r];
         PRUint32 matchedLength = range.Length();
         gfxFont *matchedFont = (range.font ? range.font.get() : nsnull);
 
         // create the glyph run for this range
         if (matchedFont) {
             aTextRun->AddGlyphRun(matchedFont, range.matchType,
                                   runStart, (matchedLength > 0));
-        } else {
-            aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup,
-                                  runStart, (matchedLength > 0));
-        }
-        if (matchedFont) {
             // do glyph layout and record the resulting positioned glyphs
             if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, aString,
                                                   runStart, matchedLength,
                                                   aRunScript)) {
                 // glyph layout failed! treat as missing glyphs
                 matchedFont = nsnull;
             }
+        } else {
+            aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup,
+                                  runStart, (matchedLength > 0));
         }
+
         if (!matchedFont) {
-            for (PRUint32 index = runStart; index < runStart + matchedLength; index++) {
-                // Record the char code so we can draw a box with the Unicode value
-                PRUint32 ch = aString[index];
-                if (NS_IS_HIGH_SURROGATE(ch) &&
-                    index + 1 < aScriptRunEnd &&
-                    NS_IS_LOW_SURROGATE(aString[index+1])) {
-                    aTextRun->SetMissingGlyph(index,
-                                              SURROGATE_TO_UCS4(aString[index],
-                                                                aString[index+1]));
-                    index++;
-                } else {
+            // for PRUnichar text, we need to set cluster boundaries so that
+            // surrogate pairs, combining characters, etc behave properly,
+            // even if we don't have glyphs for them
+            if (sizeof(T) == sizeof(PRUnichar)) {
+                gfxShapedWord::SetupClusterBoundaries(aTextRun->GetCharacterGlyphs() + runStart,
+                                                      reinterpret_cast<const PRUnichar*>(aString) + runStart,
+                                                      matchedLength);
+            }
+
+            // various "missing" characters may need special handling,
+            // so we check for them here
+            PRUint32 runLimit = runStart + matchedLength;
+            for (PRUint32 index = runStart; index < runLimit; index++) {
+                T ch = aString[index];
+
+                // tab and newline are not to be displayed as hexboxes,
+                // but do need to be recorded in the textrun
+                if (ch == '\n') {
+                    aTextRun->SetIsNewline(index);
+                    continue;
+                }
+                if (ch == '\t') {
+                    aTextRun->SetIsTab(index);
+                    continue;
+                }
+
+                // for 16-bit textruns only, check for surrogate pairs and
+                // special Unicode spaces; omit these checks in 8-bit runs
+                if (sizeof(T) == sizeof(PRUnichar)) {
+                    if (NS_IS_HIGH_SURROGATE(ch) &&
+                        index + 1 < aScriptRunEnd &&
+                        NS_IS_LOW_SURROGATE(aString[index + 1]))
+                    {
+                        aTextRun->SetMissingGlyph(index,
+                                                  SURROGATE_TO_UCS4(ch,
+                                                                    aString[index + 1]));
+                        index++;
+                        aTextRun->SetIsLowSurrogate(index);
+                        continue;
+                    }
+
+                    // check if this is a known Unicode whitespace character that
+                    // we can render using the space glyph with a custom width
                     gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch);
                     if (wid >= 0.0) {
                         nscoord advance =
                             aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5);
                         gfxTextRun::CompressedGlyph g;
                         if (gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance)) {
                             aTextRun->SetSimpleGlyph(index,
                                                      g.SetSimpleGlyph(advance,
@@ -2864,28 +3207,34 @@ gfxFontGroup::InitScriptRun(gfxContext *
                             gfxTextRun::DetailedGlyph detailedGlyph;
                             detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph();
                             detailedGlyph.mAdvance = advance;
                             detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0;
                             g.SetComplex(true, true, 1);
                             aTextRun->SetGlyphs(index,
                                                 g, &detailedGlyph);
                         }
-                    } else {
-                        aTextRun->SetMissingGlyph(index, ch);
+                        continue;
                     }
                 }
+
+                if (IsInvalidChar(ch)) {
+                    // invalid chars are left as zero-width/invisible
+                    continue;
+                }
+
+                // record char code so we can draw a box with the Unicode value
+                aTextRun->SetMissingGlyph(index, ch);
             }
         }
 
         runStart += matchedLength;
     }
 }
 
-
 already_AddRefed<gfxFont>
 gfxFontGroup::FindFontForChar(PRUint32 aCh, PRUint32 aPrevCh,
                               PRInt32 aRunScript, gfxFont *aPrevMatchedFont,
                               PRUint8 *aMatchType)
 {
     nsRefPtr<gfxFont>    selectedFont;
 
     // if this character is a join-control or the previous is a join-causer,
@@ -2950,44 +3299,49 @@ gfxFontGroup::FindFontForChar(PRUint32 a
         *aMatchType = gfxTextRange::kSystemFallback;
         selectedFont = WhichSystemFontSupportsChar(aCh);
         return selectedFont.forget();
     }
 
     return nsnull;
 }
 
-
+template<typename T>
 void gfxFontGroup::ComputeRanges(nsTArray<gfxTextRange>& aRanges,
-                                 const PRUnichar *aString,
-                                 PRUint32 begin, PRUint32 end,
+                                 const T *aString, PRUint32 aLength,
                                  PRInt32 aRunScript)
 {
-    const PRUnichar *str = aString + begin;
-    PRUint32 len = end - begin;
-
     aRanges.Clear();
 
-    if (len == 0) {
+    if (aLength == 0) {
         return;
     }
 
     PRUint32 prevCh = 0;
     gfxFont *prevFont = nsnull;
     PRUint8 matchType = 0;
 
-    for (PRUint32 i = 0; i < len; i++) {
+    for (PRUint32 i = 0; i < aLength; i++) {
 
         const PRUint32 origI = i; // save off in case we increase for surrogate
 
         // set up current ch
-        PRUint32 ch = str[i];
-        if ((i+1 < len) && NS_IS_HIGH_SURROGATE(ch) && NS_IS_LOW_SURROGATE(str[i+1])) {
-            i++;
-            ch = SURROGATE_TO_UCS4(ch, str[i]);
+        PRUint32 ch = aString[i];
+
+        // in 16-bit case only, check for surrogate pair
+        if (sizeof(T) == sizeof(PRUnichar)) {
+            if ((i + 1 < aLength) && NS_IS_HIGH_SURROGATE(ch) &&
+                                 NS_IS_LOW_SURROGATE(aString[i + 1])) {
+                i++;
+                ch = SURROGATE_TO_UCS4(ch, aString[i]);
+            }
+        }
+
+        if (ch == 0xa0) {
+            ch = ' ';
         }
 
         // find the font for this char
         nsRefPtr<gfxFont> font =
             FindFontForChar(ch, prevCh, aRunScript, prevFont, &matchType);
 
         prevCh = ch;
 
@@ -2996,28 +3350,31 @@ void gfxFontGroup::ComputeRanges(nsTArra
             aRanges.AppendElement(gfxTextRange(0, 1, font, matchType));
             prevFont = font;
         } else {
             // if font has changed, make a new range
             gfxTextRange& prevRange = aRanges[aRanges.Length() - 1];
             if (prevRange.font != font || prevRange.matchType != matchType) {
                 // close out the previous range
                 prevRange.end = origI;
-                aRanges.AppendElement(gfxTextRange(origI, i+1, font, matchType));
+                aRanges.AppendElement(gfxTextRange(origI, i + 1,
+                                                   font, matchType));
 
                 // update prevFont for the next match, *unless* we switched
                 // fonts on a ZWJ, in which case propagating the changed font
                 // is probably not a good idea (see bug 619511)
-                if (!gfxFontUtils::IsJoinCauser(ch)) {
+                if (sizeof(T) == sizeof(PRUint8) ||
+                    !gfxFontUtils::IsJoinCauser(ch))
+                {
                     prevFont = font;
                 }
             }
         }
     }
-    aRanges[aRanges.Length()-1].end = len;
+    aRanges[aRanges.Length() - 1].end = aLength;
 }
 
 gfxUserFontSet* 
 gfxFontGroup::GetUserFontSet()
 {
     return mUserFontSet;
 }
 
@@ -3301,16 +3658,240 @@ gfxFontStyle::ComputeWeight() const
     if (baseWeight < 0)
         baseWeight = 0;
     if (baseWeight > 9)
         baseWeight = 9;
 
     return baseWeight;
 }
 
+// This is not a member function of gfxShapedWord because it is also used
+// by gfxFontGroup on missing-glyph runs, where we don't actually "shape"
+// anything but still need to set cluster info.
+/*static*/ void
+gfxShapedWord::SetupClusterBoundaries(CompressedGlyph *aGlyphs,
+                                      const PRUnichar *aString, PRUint32 aLength)
+{
+    gfxTextRun::CompressedGlyph extendCluster;
+    extendCluster.SetComplex(false, true, 0);
+
+    gfxUnicodeProperties::HSType hangulState = gfxUnicodeProperties::HST_NONE;
+
+    for (PRUint32 i = 0; i < aLength; ++i) {
+        bool surrogatePair = false;
+        PRUint32 ch = aString[i];
+        if (NS_IS_HIGH_SURROGATE(ch) &&
+            i < aLength - 1 && NS_IS_LOW_SURROGATE(aString[i+1]))
+        {
+            ch = SURROGATE_TO_UCS4(ch, aString[i+1]);
+            surrogatePair = true;
+        }
+
+        PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(ch);
+        gfxUnicodeProperties::HSType hangulType = gfxUnicodeProperties::HST_NONE;
+
+        // combining marks extend the cluster
+        if (IsClusterExtender(ch)) {
+            aGlyphs[i] = extendCluster;
+        } else if (category == HB_CATEGORY_OTHER_LETTER) {
+            // handle special cases in Letter_Other category
+#if 0
+            // Currently disabled. This would follow the UAX#29 specification
+            // for extended grapheme clusters, but this is not favored by
+            // Thai users, at least for editing behavior.
+            // See discussion of equivalent Pango issue in bug 474068 and
+            // upstream at https://bugzilla.gnome.org/show_bug.cgi?id=576156.
+
+            if ((ch & ~0xff) == 0x0e00) {
+                // specific Thai & Lao (U+0Exx) chars that extend the cluster
+                if ( ch == 0x0e30 ||
+                    (ch >= 0x0e32 && ch <= 0x0e33) ||
+                     ch == 0x0e45 ||
+                     ch == 0x0eb0 ||
+                    (ch >= 0x0eb2 && ch <= 0x0eb3))
+                {
+                    if (i > 0) {
+                        aTextRun->SetGlyphs(i, extendCluster, nsnull);
+                    }
+                }
+                else if ((ch >= 0x0e40 && ch <= 0x0e44) ||
+                         (ch >= 0x0ec0 && ch <= 0x0ec4))
+                {
+                    // characters that are prepended to the following cluster
+                    if (i < length - 1) {
+                        aTextRun->SetGlyphs(i+1, extendCluster, nsnull);
+                    }
+                }
+            } else
+#endif
+            if ((ch & ~0xff) == 0x1100 ||
+                (ch >= 0xa960 && ch <= 0xa97f) ||
+                (ch >= 0xac00 && ch <= 0xd7ff))
+            {
+                // no break within Hangul syllables
+                hangulType = gfxUnicodeProperties::GetHangulSyllableType(ch);
+                switch (hangulType) {
+                case gfxUnicodeProperties::HST_L:
+                case gfxUnicodeProperties::HST_LV:
+                case gfxUnicodeProperties::HST_LVT:
+                    if (hangulState == gfxUnicodeProperties::HST_L) {
+                        aGlyphs[i] = extendCluster;
+                    }
+                    break;
+                case gfxUnicodeProperties::HST_V:
+                    if ( (hangulState != gfxUnicodeProperties::HST_NONE) &&
+                        !(hangulState & gfxUnicodeProperties::HST_T))
+                    {
+                        aGlyphs[i] = extendCluster;
+                    }
+                    break;
+                case gfxUnicodeProperties::HST_T:
+                    if (hangulState & (gfxUnicodeProperties::HST_V |
+                                       gfxUnicodeProperties::HST_T))
+                    {
+                        aGlyphs[i] = extendCluster;
+                    }
+                    break;
+                default:
+                    break;
+                }
+            }
+        }
+
+        if (surrogatePair) {
+            ++i;
+            aGlyphs[i] = extendCluster;
+        }
+
+        hangulState = hangulType;
+    }
+}
+
+gfxShapedWord::DetailedGlyph *
+gfxShapedWord::AllocateDetailedGlyphs(PRUint32 aIndex, PRUint32 aCount)
+{
+    NS_ASSERTION(aIndex < Length(), "Index out of range");
+
+    if (!mDetailedGlyphs) {
+        mDetailedGlyphs = new DetailedGlyphStore();
+    }
+
+    DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount);
+    if (!details) {
+        mCharacterGlyphs[aIndex].SetMissing(0);
+        return nsnull;
+    }
+
+    return details;
+}
+
+void
+gfxShapedWord::SetGlyphs(PRUint32 aIndex, CompressedGlyph aGlyph,
+                         const DetailedGlyph *aGlyphs)
+{
+    NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here");
+    NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(),
+                 "First character can't be a ligature continuation!");
+
+    PRUint32 glyphCount = aGlyph.GetGlyphCount();
+    if (glyphCount > 0) {
+        DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount);
+        if (!details) {
+            return;
+        }
+        memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount);
+    }
+    mCharacterGlyphs[aIndex] = aGlyph;
+}
+
+#include "ignorable.x-ccmap"
+DEFINE_X_CCMAP(gIgnorableCCMapExt, const);
+
+static inline bool
+IsDefaultIgnorable(PRUint32 aChar)
+{
+    return CCMAP_HAS_CHAR_EXT(gIgnorableCCMapExt, aChar);
+}
+
+void
+gfxShapedWord::SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar, gfxFont *aFont)
+{
+    DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+    if (!details) {
+        return;
+    }
+
+    details->mGlyphID = aChar;
+    if (IsDefaultIgnorable(aChar)) {
+        // Setting advance width to zero will prevent drawing the hexbox
+        details->mAdvance = 0;
+    } else {
+        gfxFloat width = NS_MAX(aFont->GetMetrics().aveCharWidth,
+                                gfxFontMissingGlyphs::GetDesiredMinWidth(aChar));
+        details->mAdvance = PRUint32(width * mAppUnitsPerDevUnit);
+    }
+    details->mXOffset = 0;
+    details->mYOffset = 0;
+    mCharacterGlyphs[aIndex].SetMissing(1);
+}
+
+bool
+gfxShapedWord::FilterIfIgnorable(PRUint32 aIndex)
+{
+    PRUint32 ch = GetCharAt(aIndex);
+    if (IsDefaultIgnorable(ch)) {
+        DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+        if (details) {
+            details->mGlyphID = ch;
+            details->mAdvance = 0;
+            details->mXOffset = 0;
+            details->mYOffset = 0;
+            mCharacterGlyphs[aIndex].SetMissing(1);
+            return true;
+        }
+    }
+    return false;
+}
+
+void
+gfxShapedWord::AdjustAdvancesForSyntheticBold(float aSynBoldOffset)
+{
+    PRUint32 synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit;
+    for (PRUint32 i = 0; i < Length(); ++i) {
+         CompressedGlyph *glyphData = &mCharacterGlyphs[i];
+         if (glyphData->IsSimpleGlyph()) {
+             // simple glyphs ==> just add the advance
+             PRInt32 advance = glyphData->GetSimpleAdvance() + synAppUnitOffset;
+             if (CompressedGlyph::IsSimpleAdvance(advance)) {
+                 glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
+             } else {
+                 // rare case, tested by making this the default
+                 PRUint32 glyphIndex = glyphData->GetSimpleGlyph();
+                 glyphData->SetComplex(true, true, 1);
+                 DetailedGlyph detail = {glyphIndex, advance, 0, 0};
+                 SetGlyphs(i, *glyphData, &detail);
+             }
+         } else {
+             // complex glyphs ==> add offset at cluster/ligature boundaries
+             PRUint32 detailedLength = glyphData->GetGlyphCount();
+             if (detailedLength) {
+                 DetailedGlyph *details = GetDetailedGlyphs(i);
+                 if (!details) {
+                     continue;
+                 }
+                 if (IsRightToLeft()) {
+                     details[0].mAdvance += synAppUnitOffset;
+                 } else {
+                     details[detailedLength - 1].mAdvance += synAppUnitOffset;
+                 }
+             }
+         }
+    }
+}
+
 bool
 gfxTextRun::GlyphRunIterator::NextRun()  {
     if (mNextIndex >= mTextRun->mGlyphRuns.Length())
         return false;
     mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex];
     if (mGlyphRun->mCharacterOffset >= mEndOffset)
         return false;
 
@@ -3330,169 +3911,112 @@ AccountStorageForTextRun(gfxTextRun *aTe
     // Ignores detailed glyphs... we don't know when those have been constructed
     // Also ignores gfxSkipChars dynamic storage (which won't be anything
     // for preformatted text)
     // Also ignores GlyphRun array, again because it hasn't been constructed
     // by the time this gets called. If there's only one glyphrun that's stored
     // directly in the textrun anyway so no additional overhead.
     PRUint32 length = aTextRun->GetLength();
     PRInt32 bytes = length * sizeof(gfxTextRun::CompressedGlyph);
-    if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_PERSISTENT) {
-      bytes += length * ((aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) ? 1 : 2);
-      bytes += sizeof(gfxTextRun::CompressedGlyph) - 1;
-      bytes &= ~(sizeof(gfxTextRun::CompressedGlyph) - 1);
-    }
     bytes += sizeof(gfxTextRun);
     gTextRunStorage += bytes*aSign;
     gTextRunStorageHighWaterMark = NS_MAX(gTextRunStorageHighWaterMark, gTextRunStorage);
 }
 #endif
 
-static PRUint64
-GlyphStorageAllocCount(PRUint32 aLength, PRUint32 aFlags)
-{
-    // always need to allocate storage for the glyph data
-    PRUint64 allocCount = aLength;
-
-    // if the text is not persistent, we also need space for a copy
-    if (!(aFlags & gfxTextRunFactory::TEXT_IS_PERSISTENT)) {
-        // figure out number of extra CompressedGlyph elements we need to
-        // get sufficient space for the text
-        typedef gfxTextRun::CompressedGlyph CompressedGlyph;
-        if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) {
-            allocCount += (aLength + sizeof(CompressedGlyph) - 1) /
-                          sizeof(CompressedGlyph);
-        } else {
-            allocCount += (aLength * sizeof(PRUnichar) +
-                              sizeof(CompressedGlyph) - 1) /
-                          sizeof(CompressedGlyph);
-        }
-    }
-    return allocCount;
-}
-
-// Helper for textRun creation to preallocate storage for glyphs and text;
-// this function returns a pointer to the newly-allocated glyph storage,
-// AND modifies the aText parameter if TEXT_IS_PERSISTENT was not set.
-// In that case, the text is appended to the glyph storage, so a single
-// delete[] operation in the textRun destructor will free both.
+// Helper for textRun creation to preallocate storage for glyph records;
+// this function returns a pointer to the newly-allocated glyph storage.
 // Returns nsnull if allocation fails.
-gfxTextRun::CompressedGlyph *
-gfxTextRun::AllocateStorage(const void*& aText, PRUint32 aLength, PRUint32 aFlags)
+void *
+gfxTextRun::AllocateStorageForTextRun(size_t aSize, PRUint32 aLength)
 {
-    // Here, we rely on CompressedGlyph being the largest unit we care about for
-    // allocation/alignment of either glyph data or text, so we allocate an array
-    // of CompressedGlyphs, then take the last chunk of that and cast a pointer to
-    // PRUint8* or PRUnichar* for text storage.
-
-    PRUint64 allocCount = GlyphStorageAllocCount(aLength, aFlags);
-
-    // allocate the storage we need, returning nsnull on failure rather than
-    // throwing an exception (because web content can create huge runs)
-    CompressedGlyph *storage = new (std::nothrow) CompressedGlyph[allocCount];
+    // Allocate the storage we need, returning nsnull on failure rather than
+    // throwing an exception (because web content can create huge runs).
+    void *storage = moz_malloc(aSize + aLength * sizeof(CompressedGlyph));
     if (!storage) {
-        NS_WARNING("failed to allocate glyph/text storage for text run!");
+        NS_WARNING("failed to allocate storage for text run!");
         return nsnull;
     }
 
-    // copy the text if we need to keep a copy in the textrun
-    if (!(aFlags & gfxTextRunFactory::TEXT_IS_PERSISTENT)) {
-        if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) {
-            PRUint8 *newText = reinterpret_cast<PRUint8*>(storage + aLength);
-            memcpy(newText, aText, aLength);
-            aText = newText;
-        } else {
-            PRUnichar *newText = reinterpret_cast<PRUnichar*>(storage + aLength);
-            memcpy(newText, aText, aLength*sizeof(PRUnichar));
-            aText = newText;
-        }
-    }
+    // Initialize the glyph storage (beyond aSize) to zero
+    memset(reinterpret_cast<char*>(storage) + aSize, 0,
+           aLength * sizeof(CompressedGlyph));
 
     return storage;
 }
 
 gfxTextRun *
 gfxTextRun::Create(const gfxTextRunFactory::Parameters *aParams, const void *aText,
                    PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags)
 {
-    CompressedGlyph *glyphStorage = AllocateStorage(aText, aLength, aFlags);
-    if (!glyphStorage) {
+    void *storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength);
+    if (!storage) {
         return nsnull;
     }
 
-    return new gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags, glyphStorage);
+    return new (storage) gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags);
 }
 
 gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams, const void *aText,
-                       PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags,
-                       CompressedGlyph *aGlyphStorage)
-  : mCharacterGlyphs(aGlyphStorage),
-    mUserData(aParams->mUserData),
+                       PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags)
+  : mUserData(aParams->mUserData),
     mFontGroup(aFontGroup),
     mAppUnitsPerDevUnit(aParams->mAppUnitsPerDevUnit),
-    mFlags(aFlags), mCharacterCount(aLength), mHashCode(0)
+    mFlags(aFlags), mCharacterCount(aLength)
 {
     NS_ASSERTION(mAppUnitsPerDevUnit != 0, "Invalid app unit scale");
     MOZ_COUNT_CTOR(gfxTextRun);
     NS_ADDREF(mFontGroup);
+
+    mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1);
+
     if (aParams->mSkipChars) {
         mSkipChars.TakeFrom(aParams->mSkipChars);
     }
 
-    if (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) {
-        mText.mSingle = static_cast<const PRUint8 *>(aText);
-    } else {
-        mText.mDouble = static_cast<const PRUnichar *>(aText);
-    }
 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
     AccountStorageForTextRun(this, 1);
 #endif
 
-    mUserFontSetGeneration = mFontGroup->GetGeneration();
     mSkipDrawing = mFontGroup->ShouldSkipDrawing();
 }
 
 gfxTextRun::~gfxTextRun()
 {
 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
     AccountStorageForTextRun(this, -1);
 #endif
 #ifdef DEBUG
     // Make it easy to detect a dead text run
     mFlags = 0xFFFFFFFF;
 #endif
 
-    // this will also delete the text, if it is owned by the run,
-    // because we merge the storage allocations
-    delete [] mCharacterGlyphs;
-
     NS_RELEASE(mFontGroup);
     MOZ_COUNT_DTOR(gfxTextRun);
 }
 
 bool
 gfxTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
                                    PRUint8 *aBreakBefore,
                                    gfxContext *aRefContext)
 {
     NS_ASSERTION(aStart + aLength <= mCharacterCount, "Overflow");
 
-    if (!mCharacterGlyphs)
-        return true;
     PRUint32 changed = 0;
     PRUint32 i;
+    CompressedGlyph *charGlyphs = mCharacterGlyphs + aStart;
     for (i = 0; i < aLength; ++i) {
         PRUint8 canBreak = aBreakBefore[i];
-        if (canBreak && !mCharacterGlyphs[aStart + i].IsClusterStart()) {
+        if (canBreak && !charGlyphs[i].IsClusterStart()) {
             // This can happen ... there is no guarantee that our linebreaking rules
             // align with the platform's idea of what constitutes a cluster.
             NS_WARNING("Break suggested inside cluster!");
             canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE;
         }
-        changed |= mCharacterGlyphs[aStart + i].SetCanBreakBefore(canBreak);
+        changed |= charGlyphs[i].SetCanBreakBefore(canBreak);
     }
     return changed != 0;
 }
 
 gfxTextRun::LigatureData
 gfxTextRun::ComputeLigatureData(PRUint32 aPartStart, PRUint32 aPartEnd,
                                 PropertyProvider *aProvider)
 {
@@ -3798,71 +4322,16 @@ struct BufferAlphaColor {
         mContext->Restore();
     }
 
     gfxContext *mContext;
     gfxFloat mAlpha;
 };
 
 void
-gfxTextRun::AdjustAdvancesForSyntheticBold(gfxContext *aContext,
-                                           PRUint32 aStart,
-                                           PRUint32 aLength)
-{
-    const PRUint32 appUnitsPerDevUnit = GetAppUnitsPerDevUnit();
-    bool isRTL = IsRightToLeft();
-
-    GlyphRunIterator iter(this, aStart, aLength);
-    while (iter.NextRun()) {
-        gfxFont *font = iter.GetGlyphRun()->mFont;
-        if (font->IsSyntheticBold()) {
-            PRUint32 synAppUnitOffset =
-                font->GetSyntheticBoldOffset() *
-                    appUnitsPerDevUnit * CalcXScale(aContext);
-            PRUint32 start = iter.GetStringStart();
-            PRUint32 end = iter.GetStringEnd();
-            PRUint32 i;
-            
-            // iterate over glyphs, start to end
-            for (i = start; i < end; ++i) {
-                gfxTextRun::CompressedGlyph *glyphData = &mCharacterGlyphs[i];
-                
-                if (glyphData->IsSimpleGlyph()) {
-                    // simple glyphs ==> just add the advance
-                    PRInt32 advance = glyphData->GetSimpleAdvance() + synAppUnitOffset;
-                    if (CompressedGlyph::IsSimpleAdvance(advance)) {
-                        glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
-                    } else {
-                        // rare case, tested by making this the default
-                        PRUint32 glyphIndex = glyphData->GetSimpleGlyph();
-                        glyphData->SetComplex(true, true, 1);
-                        DetailedGlyph detail = {glyphIndex, advance, 0, 0};
-                        SetGlyphs(i, *glyphData, &detail);
-                    }
-                } else {
-                    // complex glyphs ==> add offset at cluster/ligature boundaries
-                    PRUint32 detailedLength = glyphData->GetGlyphCount();
-                    if (detailedLength) {
-                        gfxTextRun::DetailedGlyph *details = GetDetailedGlyphs(i);
-                        if (!details) {
-                            continue;
-                        }
-                        if (isRTL) {
-                            details[0].mAdvance += synAppUnitOffset;
-                        } else {
-                            details[detailedLength - 1].mAdvance += synAppUnitOffset;
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-void
 gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt,
                  PRUint32 aStart, PRUint32 aLength,
                  PropertyProvider *aProvider, gfxFloat *aAdvanceWidth)
 {
     NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range");
 
     gfxFloat direction = GetDirection();
 
@@ -4160,17 +4629,17 @@ gfxTextRun::BreakAndMeasureText(PRUint32
                 charAdvance += space->mBefore + space->mAfter;
             }
         } else {
             charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider);
         }
         
         advance += charAdvance;
         if (aTrimWhitespace) {
-            if (GetChar(i) == ' ') {
+            if (mCharacterGlyphs[i].CharIsSpace()) {
                 ++trimmableChars;
                 trimmableAdvance += charAdvance;
             } else {
                 trimmableAdvance = 0;
                 trimmableChars = 0;
             }
         }
     }
@@ -4284,16 +4753,20 @@ gfxTextRun::FindFirstGlyphRunContaining(
                  "Hmm, something went wrong, aOffset should have been found");
     return start;
 }
 
 nsresult
 gfxTextRun::AddGlyphRun(gfxFont *aFont, PRUint8 aMatchType,
                         PRUint32 aUTF16Offset, bool aForceNewRun)
 {
+    NS_ASSERTION(aFont, "adding glyph run for null font!");
+    if (!aFont) {
+        return NS_OK;
+    }    
     PRUint32 numGlyphRuns = mGlyphRuns.Length();
     if (!aForceNewRun && numGlyphRuns > 0) {
         GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1];
 
         NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset,
                      "Glyph runs out of order (and run not forced)");
 
         // Don't append a run if the font is already the one we want
@@ -4373,19 +4846,20 @@ gfxTextRun::SanitizeGlyphRuns()
     if (mGlyphRuns.Length() <= 1)
         return;
 
     // If any glyph run starts with ligature-continuation characters, we need to advance it
     // to the first "real" character to avoid drawing partial ligature glyphs from wrong font
     // (seen with U+FEFF in reftest 474417-1, as Core Text eliminates the glyph, which makes
     // it appear as if a ligature has been formed)
     PRInt32 i, lastRunIndex = mGlyphRuns.Length() - 1;
+    const CompressedGlyph *charGlyphs = mCharacterGlyphs;
     for (i = lastRunIndex; i >= 0; --i) {
         GlyphRun& run = mGlyphRuns[i];
-        while (mCharacterGlyphs[run.mCharacterOffset].IsLigatureContinuation() &&
+        while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() &&
                run.mCharacterOffset < mCharacterCount) {
             run.mCharacterOffset++;
         }
         // if the run has become empty, eliminate it
         if ((i < lastRunIndex &&
              run.mCharacterOffset >= mGlyphRuns[i+1].mCharacterOffset) ||
             (i == lastRunIndex && run.mCharacterOffset == mCharacterCount)) {
             mGlyphRuns.RemoveElementAt(i);
@@ -4407,20 +4881,16 @@ gfxTextRun::CountMissingGlyphs()
     return count;
 }
 
 gfxTextRun::DetailedGlyph *
 gfxTextRun::AllocateDetailedGlyphs(PRUint32 aIndex, PRUint32 aCount)
 {
     NS_ASSERTION(aIndex < mCharacterCount, "Index out of range");
 
-    if (!mCharacterGlyphs) {
-        return nsnull;
-    }
-
     if (!mDetailedGlyphs) {
         mDetailedGlyphs = new DetailedGlyphStore();
     }
 
     DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount);
     if (!details) {
         mCharacterGlyphs[aIndex].SetMissing(0);
         return nsnull;
@@ -4429,42 +4899,39 @@ gfxTextRun::AllocateDetailedGlyphs(PRUin
     return details;
 }
 
 void
 gfxTextRun::SetGlyphs(PRUint32 aIndex, CompressedGlyph aGlyph,
                       const DetailedGlyph *aGlyphs)
 {
     NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here");
-    NS_ASSERTION(aIndex > 0 ||
-                 (aGlyph.IsClusterStart() && aGlyph.IsLigatureGroupStart()),
-                 "First character must be the start of a cluster and can't be a ligature continuation!");
+    NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(),
+                 "First character can't be a ligature continuation!");
 
     PRUint32 glyphCount = aGlyph.GetGlyphCount();
     if (glyphCount > 0) {
         DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount);
         if (!details)
             return;
         memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount);
     }
     mCharacterGlyphs[aIndex] = aGlyph;
 }
 
-#include "ignorable.x-ccmap"
-DEFINE_X_CCMAP(gIgnorableCCMapExt, const);
-
-static inline bool
-IsDefaultIgnorable(PRUint32 aChar)
-{
-    return CCMAP_HAS_CHAR_EXT(gIgnorableCCMapExt, aChar);
-}
-
 void
 gfxTextRun::SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar)
 {
+    PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(aChar);
+    if (category >= HB_CATEGORY_COMBINING_MARK &&
+        category <= HB_CATEGORY_NON_SPACING_MARK)
+    {
+        mCharacterGlyphs[aIndex].SetComplex(false, true, 0);
+    }
+
     DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
     if (!details)
         return;
 
     details->mGlyphID = aChar;
     GlyphRun *glyphRun = &mGlyphRuns[FindFirstGlyphRunContaining(aIndex)];
     if (IsDefaultIgnorable(aChar)) {
         // Setting advance width to zero will prevent drawing the hexbox
@@ -4474,68 +4941,78 @@ gfxTextRun::SetMissingGlyph(PRUint32 aIn
                                 gfxFontMissingGlyphs::GetDesiredMinWidth(aChar));
         details->mAdvance = PRUint32(width*GetAppUnitsPerDevUnit());
     }
     details->mXOffset = 0;
     details->mYOffset = 0;
     mCharacterGlyphs[aIndex].SetMissing(1);
 }
 
-bool
-gfxTextRun::FilterIfIgnorable(PRUint32 aIndex)
+void
+gfxTextRun::CopyGlyphDataFrom(const gfxShapedWord *aShapedWord, PRUint32 aOffset)
 {
-    PRUint32 ch = GetChar(aIndex);
-    if (IsDefaultIgnorable(ch)) {
-        DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
-        if (details) {
-            details->mGlyphID = ch;
-            details->mAdvance = 0;
-            details->mXOffset = 0;
-            details->mYOffset = 0;
-            mCharacterGlyphs[aIndex].SetMissing(1);
-            return true;
+    PRUint32 wordLen = aShapedWord->Length();
+    NS_ASSERTION(aOffset + wordLen <= GetLength(),
+                 "word overruns end of textrun!");
+
+    const CompressedGlyph *wordGlyphs = aShapedWord->GetCharacterGlyphs();
+    if (aShapedWord->HasDetailedGlyphs()) {
+        for (PRUint32 i = 0; i < wordLen; ++i, ++aOffset) {
+            const CompressedGlyph& g = wordGlyphs[i];
+            if (g.IsSimpleGlyph()) {
+                SetSimpleGlyph(aOffset, g);
+            } else {
+                const DetailedGlyph *details =
+                    g.GetGlyphCount() > 0 ?
+                        aShapedWord->GetDetailedGlyphs(i) : nsnull;
+                SetGlyphs(aOffset, g, details);
+            }
         }
-    }
-    return false;
+    } else {
+        memcpy(GetCharacterGlyphs() + aOffset, wordGlyphs,
+               wordLen * sizeof(CompressedGlyph));
+    }
 }
 
 void
 gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart,
                               PRUint32 aLength, PRUint32 aDest)
 {
     NS_ASSERTION(aStart + aLength <= aSource->GetLength(),
                  "Source substring out of range");
     NS_ASSERTION(aDest + aLength <= GetLength(),
                  "Destination substring out of range");
 
     if (aSource->mSkipDrawing) {
         mSkipDrawing = true;
     }
 
     // Copy base glyph data, and DetailedGlyph data where present
+    const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aStart;
+    CompressedGlyph *dstGlyphs = mCharacterGlyphs + aDest;
     for (PRUint32 i = 0; i < aLength; ++i) {
-        CompressedGlyph g = aSource->mCharacterGlyphs[i + aStart];
-        g.SetCanBreakBefore(mCharacterGlyphs[i + aDest].CanBreakBefore());
+        CompressedGlyph g = srcGlyphs[i];
+        g.SetCanBreakBefore(dstGlyphs[i].CanBreakBefore());
         if (!g.IsSimpleGlyph()) {
             PRUint32 count = g.GetGlyphCount();
             if (count > 0) {
                 DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count);
                 if (dst) {
                     DetailedGlyph *src = aSource->GetDetailedGlyphs(i + aStart);
                     if (src) {
                         ::memcpy(dst, src, count * sizeof(DetailedGlyph));
                     } else {
                         g.SetMissing(0);
                     }
                 } else {
                     g.SetMissing(0);
                 }
             }
         }
-        mCharacterGlyphs[i + aDest] = g;
+        dstGlyphs[i] = g;
     }
 
     // Copy glyph runs
     GlyphRunIterator iter(aSource, aStart, aLength);
 #ifdef DEBUG
     gfxFont *lastFont = nsnull;
 #endif
     while (iter.NextRun()) {
@@ -4564,41 +5041,62 @@ gfxTextRun::CopyGlyphDataFrom(gfxTextRun
         nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType,
                                   start - aStart + aDest, false);
         if (NS_FAILED(rv))
             return;
     }
 }
 
 void
-gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, PRUint32 aCharIndex)
+gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext,
+                          PRUint32 aCharIndex)
+{
+    if (SetSpaceGlyphIfSimple(aFont, aContext, aCharIndex, ' ')) {
+        return;
+    }
+
+    aFont->InitWordCache();
+    static const PRUint8 space = ' ';
+    gfxShapedWord *sw = aFont->GetShapedWord(aContext,
+                                             &space, 1,
+                                             HashMix(0, ' '), 
+                                             HB_SCRIPT_LATIN,
+                                             mAppUnitsPerDevUnit,
+                                             gfxTextRunFactory::TEXT_IS_8BIT |
+                                             gfxTextRunFactory::TEXT_IS_ASCII |
+                                             gfxTextRunFactory::TEXT_IS_PERSISTENT);
+    if (sw) {
+        AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false);
+        CopyGlyphDataFrom(sw, aCharIndex);
+    }
+}
+
+bool
+gfxTextRun::SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext,
+                                  PRUint32 aCharIndex, PRUnichar aSpaceChar)
 {
     PRUint32 spaceGlyph = aFont->GetSpaceGlyph();
-    float spaceWidth = aFont->GetMetrics().spaceWidth;
-    PRUint32 spaceWidthAppUnits = NS_lroundf(spaceWidth*mAppUnitsPerDevUnit);
-    if (!spaceGlyph ||
-        !CompressedGlyph::IsSimpleGlyphID(spaceGlyph) ||
-        !CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) {
-        gfxTextRunFactory::Parameters params = {
-            aContext, nsnull, nsnull, nsnull, 0, mAppUnitsPerDevUnit
-        };
-        static const PRUint8 space = ' ';
-        nsAutoPtr<gfxTextRun> textRun;
-        textRun = mFontGroup->MakeTextRun(&space, 1, &params,
-            gfxTextRunFactory::TEXT_IS_8BIT | gfxTextRunFactory::TEXT_IS_ASCII |
-            gfxTextRunFactory::TEXT_IS_PERSISTENT);
-        if (!textRun || !textRun->mCharacterGlyphs)
-            return;
-        CopyGlyphDataFrom(textRun, 0, 1, aCharIndex);
-        return;
-    }
+    if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) {
+        return false;
+    }
+
+    PRUint32 spaceWidthAppUnits =
+        NS_lroundf(aFont->GetMetrics().spaceWidth * mAppUnitsPerDevUnit);
+    if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) {
+        return false;
+    }
+
     AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false);
     CompressedGlyph g;
     g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph);
+    if (aSpaceChar == ' ') {
+        g.SetIsSpace();
+    }
     SetSimpleGlyph(aCharIndex, g);
+    return true;
 }
 
 void
 gfxTextRun::FetchGlyphExtents(gfxContext *aRefContext)
 {
     bool needsGlyphExtents = NeedsGlyphExtents(this);
     if (!needsGlyphExtents && !mDetailedGlyphs)
         return;
@@ -4711,56 +5209,44 @@ gfxTextRun::ClusterIterator::ClusterAdva
     return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider);
 }
 
 size_t
 gfxTextRun::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf)
 {
     // The second arg is how much gfxTextRun::AllocateStorage would have
     // allocated.
-    size_t total =
-        aMallocSizeOf(mCharacterGlyphs,
-                      sizeof(CompressedGlyph) *
-                      GlyphStorageAllocCount(mCharacterCount, mFlags));
+    size_t total = mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf);
 
     if (mDetailedGlyphs) {
         total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf);
     }
 
-    total += mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf);
-
     return total;
 }
 
 size_t
 gfxTextRun::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf)
 {
-    return aMallocSizeOf(this, sizeof(gfxTextRun)) +
+    // The second arg is how much gfxTextRun::AllocateStorageForTextRun would
+    // have allocated, given the character count of this run.
+    return aMallocSizeOf(this, sizeof(gfxTextRun) + sizeof(CompressedGlyph) * GetLength()) +
            SizeOfExcludingThis(aMallocSizeOf);
 }
 
 
 #ifdef DEBUG
 void
 gfxTextRun::Dump(FILE* aOutput) {
     if (!aOutput) {
         aOutput = stdout;
     }
 
     PRUint32 i;
-    fputc('"', aOutput);
-    for (i = 0; i < mCharacterCount; ++i) {
-        PRUnichar ch = GetChar(i);
-        if (ch >= 32 && ch < 128) {
-            fputc(ch, aOutput);
-        } else {
-            fprintf(aOutput, "\\u%4x", ch);
-        }
-    }
-    fputs("\" [", aOutput);
+    fputc('[', aOutput);
     for (i = 0; i < mGlyphRuns.Length(); ++i) {
         if (i > 0) {
             fputc(',', aOutput);
         }
         gfxFont* font = mGlyphRuns[i].mFont;
         const gfxFontStyle* style = font->GetStyle();
         NS_ConvertUTF16toUTF8 fontName(font->GetName());
         nsCAutoString lang;
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -66,16 +66,17 @@ typedef struct _cairo_scaled_font cairo_
 
 class gfxContext;
 class gfxTextRun;
 class gfxFont;
 class gfxFontFamily;
 class gfxFontGroup;
 class gfxUserFontSet;
 class gfxUserFontData;
+class gfxShapedWord;
 
 class nsILanguageAtomService;
 
 typedef struct _hb_blob_t hb_blob_t;
 
 // We should eliminate these synonyms when it won't cause many merge conflicts.
 #define FONT_STYLE_NORMAL              NS_FONT_STYLE_NORMAL
 #define FONT_STYLE_ITALIC              NS_FONT_STYLE_ITALIC
@@ -665,49 +666,54 @@ struct gfxTextRange {
  * When a font's refcount decreases to zero, instead of deleting it we
  * add it to our expiration tracker.
  * The expiration tracker tracks fonts with zero refcount. After a certain
  * period of time, such fonts expire and are deleted.
  *
  * We're using 3 generations with a ten-second generation interval, so
  * zero-refcount fonts will be deleted 20-30 seconds after their refcount
  * goes to zero, if timer events fire in a timely manner.
+ *
+ * The font cache also handles timed expiration of cached ShapedWords
+ * for "persistent" fonts: it has a repeating timer, and notifies
+ * each cached font to "age" its shaped words. The words will be released
+ * by the fonts if they get aged three times without being re-used in the
+ * meantime.
+ *
+ * Note that the ShapedWord timeout is much larger than the font timeout,
+ * so that in the case of a short-lived font, we'll discard the gfxFont
+ * completely, with all its words, and avoid the cost of aging the words
+ * individually. That only happens with longer-lived fonts.
  */
 class THEBES_API gfxFontCache : public nsExpirationTracker<gfxFont,3> {
 public:
-    enum { TIMEOUT_SECONDS = 10 };
-    gfxFontCache()
-        : nsExpirationTracker<gfxFont,3>(TIMEOUT_SECONDS*1000) { mFonts.Init(); }
-    ~gfxFontCache() {
-        // Expire everything that has a zero refcount, so we don't leak them.
-        AgeAllGenerations();
-        // All fonts should be gone.
-        NS_WARN_IF_FALSE(mFonts.Count() == 0,
-                         "Fonts still alive while shutting down gfxFontCache");
-        // Note that we have to delete everything through the expiration
-        // tracker, since there might be fonts not in the hashtable but in
-        // the tracker.
-    }
+    enum {
+        FONT_TIMEOUT_SECONDS = 10,
+        SHAPED_WORD_TIMEOUT_SECONDS = 60
+    };
+
+    gfxFontCache();
+    ~gfxFontCache();
 
     /*
      * Get the global gfxFontCache.  You must call Init() before
      * calling this method --- the result will not be null.
      */
     static gfxFontCache* GetCache() {
         return gGlobalCache;
     }
 
     static nsresult Init();
     // It's OK to call this even if Init() has not been called.
     static void Shutdown();
 
     // Look up a font in the cache. Returns an addrefed pointer, or null
     // if there's nothing matching in the cache
     already_AddRefed<gfxFont> Lookup(const gfxFontEntry *aFontEntry,
-                                     const gfxFontStyle *aFontGroup);
+                                     const gfxFontStyle *aStyle);
     // We created a new font (presumably because Lookup returned null);
     // put it in the cache. The font's refcount should be nonzero. It is
     // allowable to add a new font even if there is one already in the
     // cache with the same key; we'll forget about the old one.
     void AddNew(gfxFont *aFont);
 
     // The font's refcount has gone to zero; give ownership of it to
     // the cache. We delete it if it's not acquired again after a certain
@@ -755,16 +761,129 @@ protected:
             return NS_PTR_TO_INT32(aKey->mFontEntry) ^ aKey->mStyle->Hash();
         }
         enum { ALLOW_MEMMOVE = true };
 
         gfxFont* mFont;
     };
 
     nsTHashtable<HashEntry> mFonts;
+
+    static PLDHashOperator AgeCachedWordsForFont(HashEntry* aHashEntry, void*);
+    static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache);
+    nsCOMPtr<nsITimer>      mWordCacheExpirationTimer;
+};
+
+class THEBES_API gfxTextRunFactory {
+    NS_INLINE_DECL_REFCOUNTING(gfxTextRunFactory)
+
+public:
+    // Flags in the mask 0xFFFF0000 are reserved for textrun clients
+    // Flags in the mask 0x0000F000 are reserved for per-platform fonts
+    // Flags in the mask 0x00000FFF are set by the textrun creator.
+    enum {
+        CACHE_TEXT_FLAGS    = 0xF0000000,
+        USER_TEXT_FLAGS     = 0x0FFF0000,
+        PLATFORM_TEXT_FLAGS = 0x0000F000,
+        TEXTRUN_TEXT_FLAGS  = 0x00000FFF,
+        SETTABLE_FLAGS      = CACHE_TEXT_FLAGS | USER_TEXT_FLAGS,
+
+        /**
+         * When set, the text string pointer used to create the text run
+         * is guaranteed to be available during the lifetime of the text run.
+         */
+        TEXT_IS_PERSISTENT           = 0x0001,
+        /**
+         * When set, the text is known to be all-ASCII (< 128).
+         */
+        TEXT_IS_ASCII                = 0x0002,
+        /**
+         * When set, the text is RTL.
+         */
+        TEXT_IS_RTL                  = 0x0004,
+        /**
+         * When set, spacing is enabled and the textrun needs to call GetSpacing
+         * on the spacing provider.
+         */
+        TEXT_ENABLE_SPACING          = 0x0008,
+        /**
+         * When set, GetHyphenationBreaks may return true for some character
+         * positions, otherwise it will always return false for all characters.
+         */
+        TEXT_ENABLE_HYPHEN_BREAKS    = 0x0010,
+        /**
+         * When set, the text has no characters above 255 and it is stored
+         * in the textrun in 8-bit format.
+         */
+        TEXT_IS_8BIT                 = 0x0020,
+        /**
+         * When set, the RunMetrics::mBoundingBox field will be initialized
+         * properly based on glyph extents, in particular, glyph extents that
+         * overflow the standard font-box (the box defined by the ascent, descent
+         * and advance width of the glyph). When not set, it may just be the
+         * standard font-box even if glyphs overflow.
+         */
+        TEXT_NEED_BOUNDING_BOX       = 0x0040,
+        /**
+         * When set, optional ligatures are disabled. Ligatures that are
+         * required for legible text should still be enabled.
+         */
+        TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0080,
+        /**
+         * When set, the textrun should favour speed of construction over
+         * quality. This may involve disabling ligatures and/or kerning or
+         * other effects.
+         */
+        TEXT_OPTIMIZE_SPEED          = 0x0100,
+        /**
+         * For internal use by the memory reporter when accounting for
+         * storage used by textruns.
+         * Because the reporter may visit each textrun multiple times while
+         * walking the frame trees and textrun cache, it needs to mark
+         * textruns that have been seen so as to avoid multiple-accounting.
+         */
+        TEXT_RUN_SIZE_ACCOUNTED      = 0x0200,
+
+        /**
+         * nsTextFrameThebes sets these, but they're defined here rather than
+         * in nsTextFrameUtils.h because ShapedWord creation/caching also needs
+         * to check the _INCOMING flag
+         */
+        TEXT_TRAILING_ARABICCHAR = 0x20000000,
+        /**
+         * When set, the previous character for this textrun was an Arabic
+         * character.  This is used for the context detection necessary for
+         * bidi.numeral implementation.
+         */
+        TEXT_INCOMING_ARABICCHAR = 0x40000000,
+
+        TEXT_UNUSED_FLAGS = 0x90000000
+    };
+
+    /**
+     * This record contains all the parameters needed to initialize a textrun.
+     */
+    struct Parameters {
+        // A reference context suggesting where the textrun will be rendered
+        gfxContext   *mContext;
+        // Pointer to arbitrary user data (which should outlive the textrun)
+        void         *mUserData;
+        // A description of which characters have been stripped from the original
+        // DOM string to produce the characters in the textrun. May be null
+        // if that information is not relevant.
+        gfxSkipChars *mSkipChars;
+        // A list of where linebreaks are currently placed in the textrun. May
+        // be null if mInitialBreakCount is zero.
+        PRUint32     *mInitialBreaks;
+        PRUint32      mInitialBreakCount;
+        // The ratio to use to convert device pixels to application layout units
+        PRUint32      mAppUnitsPerDevUnit;
+    };
+
+    virtual ~gfxTextRunFactory() {}
 };
 
 /**
  * This stores glyph bounds information for a particular gfxFont, at
  * a particular appunits-per-dev-pixel ratio (because the compressed glyph
  * width array is stored in appunits).
  * 
  * We store a hashtable from glyph IDs to float bounding rects. For the
@@ -902,22 +1021,19 @@ public:
     gfxFontShaper(gfxFont *aFont)
         : mFont(aFont)
     {
         NS_ASSERTION(aFont, "shaper requires a valid font!");
     }
 
     virtual ~gfxFontShaper() { }
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript) = 0;
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aText) = 0;
 
     gfxFont *GetFont() const { return mFont; }
 
 protected:
     // the font this shaper is working with
     gfxFont * mFont;
 };
 
@@ -1247,26 +1363,140 @@ public:
             return 0;
         }
         return mFontEntry->GetUVSGlyph(aCh, aVS); 
     }
 
     // call the (virtual) InitTextRun method to do glyph generation/shaping,
     // limiting the length of text passed by processing the run in multiple
     // segments if necessary
+    template<typename T>
     bool SplitAndInitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+                             gfxTextRun *aTextRun,
+                             const T *aString,
+                             PRUint32 aRunStart,
+                             PRUint32 aRunLength,
+                             PRInt32 aRunScript);
+
+    // Get a ShapedWord representing the given text (either 8- or 16-bit)
+    // for use in setting up a gfxTextRun.
+    template<typename T>
+    gfxShapedWord* GetShapedWord(gfxContext *aContext,
+                                 const T *aText,
+                                 PRUint32 aLength,
+                                 PRUint32 aHash,
+                                 PRInt32 aRunScript,
+                                 PRInt32 aAppUnitsPerDevUnit,
+                                 PRUint32 aFlags);
+
+    // Ensure the ShapedWord cache is initialized. This MUST be called before
+    // any attempt to use GetShapedWord().
+    void InitWordCache() {
+        if (!mWordCache.IsInitialized()) {
+            mWordCache.Init();
+        }
+    }
+
+    // Called by the gfxFontCache timer to increment the age of all the words,
+    // so that they'll expire after a sufficient period of non-use
+    void AgeCachedWords() {
+        if (mWordCache.IsInitialized()) {
+            (void)mWordCache.EnumerateEntries(AgeCacheEntry, this);
+        }
+    }
 
 protected:
+    // Call the appropriate shaper to generate glyphs for aText and store
+    // them into aShapedWord.
+    // The length of the text is aShapedWord->Length().
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aText,
+                           bool aPreferPlatformShaping = false);
+
     nsRefPtr<gfxFontEntry> mFontEntry;
 
+    struct CacheHashKey {
+        union {
+            const PRUint8   *mSingle;
+            const PRUnichar *mDouble;
+        }                mText;
+        PRUint32         mLength;
+        PRUint32         mFlags;
+        PRInt32          mScript;
+        PRInt32          mAppUnitsPerDevUnit;
+        PLDHashNumber    mHashKey;
+        bool             mTextIs8Bit;
+
+        CacheHashKey(const PRUint8 *aText, PRUint32 aLength,
+                     PRUint32 aStringHash,
+                     PRInt32 aScriptCode, PRInt32 aAppUnitsPerDevUnit,
+                     PRUint32 aFlags)
+            : mLength(aLength),
+              mFlags(aFlags),
+              mScript(aScriptCode),
+              mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
+              mHashKey(aStringHash + aScriptCode +
+                  aAppUnitsPerDevUnit * 0x100 + aFlags * 0x10000),
+              mTextIs8Bit(true)
+        {
+            NS_ASSERTION(aFlags & gfxTextRunFactory::TEXT_IS_8BIT,
+                         "8-bit flag should have been set");
+            mText.mSingle = aText;
+        }
+
+        CacheHashKey(const PRUnichar *aText, PRUint32 aLength,
+                     PRUint32 aStringHash,
+                     PRInt32 aScriptCode, PRInt32 aAppUnitsPerDevUnit,
+                     PRUint32 aFlags)
+            : mLength(aLength),
+              mFlags(aFlags),
+              mScript(aScriptCode),
+              mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
+              mHashKey(aStringHash + aScriptCode +
+                  aAppUnitsPerDevUnit * 0x100 + aFlags * 0x10000),
+              mTextIs8Bit(false)
+        {
+            // We can NOT assert that TEXT_IS_8BIT is false in aFlags here,
+            // because this might be an 8bit-only word from a 16-bit textrun,
+            // in which case the text we're passed is still in 16-bit form,
+            // and we'll have to use an 8-to-16bit comparison in KeyEquals.
+            mText.mDouble = aText;
+        }
+    };
+
+    class CacheHashEntry : public PLDHashEntryHdr {
+    public:
+        typedef const CacheHashKey &KeyType;
+        typedef const CacheHashKey *KeyTypePointer;
+
+        // When constructing a new entry in the hashtable, the caller of Put()
+        // will fill us in.
+        CacheHashEntry(KeyTypePointer aKey) { }
+        CacheHashEntry(const CacheHashEntry& toCopy) { NS_ERROR("Should not be called"); }
+        ~CacheHashEntry() { }
+
+        bool KeyEquals(const KeyTypePointer aKey) const;
+
+        static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+        static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+            return aKey->mHashKey;
+        }
+
+        enum { ALLOW_MEMMOVE = true };
+
+        nsAutoPtr<gfxShapedWord> mShapedWord;
+    };
+
+    nsTHashtable<CacheHashEntry> mWordCache;
+
+    static PLDHashOperator AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData);
+    static const PRUint32  kShapedWordCacheMaxAge = 3;
+
     bool                       mIsValid;
 
     // use synthetic bolding for environments where this is not supported
     // by the platform
     bool                       mApplySyntheticBold;
 
     nsExpirationState          mExpirationState;
     gfxFontStyle               mStyle;
@@ -1309,125 +1539,619 @@ protected:
     // Helper to calculate various derived metrics from the results of
     // InitMetricsFromSfntTables or equivalent platform code
     void CalculateDerivedMetrics(Metrics& aMetrics);
 
     // some fonts have bad metrics, this method sanitize them.
     // if this font has bad underline offset, aIsBadUnderlineFont should be true.
     void SanitizeMetrics(gfxFont::Metrics *aMetrics, bool aIsBadUnderlineFont);
 
-    // Default simply calls m[Platform|HarfBuzz]Shaper->InitTextRun().
-    // Override if the font class wants to give special handling
-    // to shaper failure.
-    // Returns false if shaping failed (though currently we
-    // don't have any good way to handle that situation).
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript,
-                               bool aPreferPlatformShaping = false);
+    // Bug 674909. When synthetic bolding text by drawing twice, need to
+    // render using a pixel offset in device pixels, otherwise text
+    // doesn't appear bolded, it appears as if a bad text shadow exists
+    // when a non-identity transform exists.  Use an offset factor so that
+    // the second draw occurs at a constant offset in device pixels.
+    // This helper calculates the scale factor we need to apply to the
+    // synthetic-bold offset.
+    static double CalcXScale(gfxContext *aContext);
 };
 
 // proportion of ascent used for x-height, if unable to read value from font
 #define DEFAULT_XHEIGHT_FACTOR 0.56f
 
-class THEBES_API gfxTextRunFactory {
-    NS_INLINE_DECL_REFCOUNTING(gfxTextRunFactory)
-
+/*
+ * gfxShapedWord stores a list of zero or more glyphs for each character. For each
+ * glyph we store the glyph ID, the advance, and possibly an xoffset and yoffset.
+ * The idea is that a string is rendered by a loop that draws each glyph
+ * at its designated offset from the current point, then advances the current
+ * point by the glyph's advance in the direction of the textrun (LTR or RTL).
+ * Each glyph advance is always rounded to the nearest appunit; this ensures
+ * consistent results when dividing the text in a textrun into multiple text
+ * frames (frame boundaries are always aligned to appunits). We optimize
+ * for the case where a character has a single glyph and zero xoffset and yoffset,
+ * and the glyph ID and advance are in a reasonable range so we can pack all
+ * necessary data into 32 bits.
+ *
+ * This glyph data is copied into gfxTextRuns as needed from the cache of
+ * ShapedWords associated with each gfxFont instance.
+ *
+ * gfxTextRun methods that measure or draw substrings will associate all the
+ * glyphs in a cluster with the first character of the cluster; if that character
+ * is in the substring, the glyphs will be measured or drawn, otherwise they
+ * won't.
+ */
+class gfxShapedWord
+{
 public:
-    // Flags in the mask 0xFFFF0000 are reserved for textrun clients
-    // Flags in the mask 0x0000F000 are reserved for per-platform fonts
-    // Flags in the mask 0x00000FFF are set by the textrun creator.
-    enum {
-        CACHE_TEXT_FLAGS    = 0xF0000000,
-        USER_TEXT_FLAGS     = 0x0FFF0000,
-        PLATFORM_TEXT_FLAGS = 0x0000F000,
-        TEXTRUN_TEXT_FLAGS  = 0x00000FFF,
-        SETTABLE_FLAGS      = CACHE_TEXT_FLAGS | USER_TEXT_FLAGS,
-
-        /**
-         * When set, the text string pointer used to create the text run
-         * is guaranteed to be available during the lifetime of the text run.
-         */
-        TEXT_IS_PERSISTENT           = 0x0001,
-        /**
-         * When set, the text is known to be all-ASCII (< 128).
-         */
-        TEXT_IS_ASCII                = 0x0002,
-        /**
-         * When set, the text is RTL.
-         */
-        TEXT_IS_RTL                  = 0x0004,
-        /**
-         * When set, spacing is enabled and the textrun needs to call GetSpacing
-         * on the spacing provider.
-         */
-        TEXT_ENABLE_SPACING          = 0x0008,
+    static const PRUint32 kMaxLength = 0x7fff;
+
+    // Create a ShapedWord that can hold glyphs for aLength characters,
+    // with mCharacterGlyphs sized appropriately.
+    //
+    // Returns null on allocation failure (does NOT use infallible alloc)
+    // so caller must check for success.
+    //
+    // This does NOT perform shaping, so the returned word contains no
+    // glyph data; the caller must call gfxFont::Shape() with appropriate
+    // parameters to set up the glyphs.
+    static gfxShapedWord* Create(const PRUint8 *aText, PRUint32 aLength,
+                                 PRInt32 aRunScript,
+                                 PRInt32 aAppUnitsPerDevUnit,
+                                 PRUint32 aFlags) {
+        NS_ASSERTION(aLength <= kMaxLength, "excessive length for gfxShapedWord!");
+
+        // Compute size needed including the mCharacterGlyphs array
+        // and a copy of the original text
+        PRUint32 size =
+            offsetof(gfxShapedWord, mCharacterGlyphs) +
+            aLength * (sizeof(CompressedGlyph) + sizeof(PRUint8));
+        void *storage = moz_malloc(size);
+        if (!storage) {
+            return nsnull;
+        }
+
+        // Construct in the pre-allocated storage, using placement new
+        return new (storage) gfxShapedWord(aText, aLength, aRunScript,
+                                           aAppUnitsPerDevUnit, aFlags);
+    }
+
+    static gfxShapedWord* Create(const PRUnichar *aText, PRUint32 aLength,
+                                 PRInt32 aRunScript,
+                                 PRInt32 aAppUnitsPerDevUnit,
+                                 PRUint32 aFlags) {
+        NS_ASSERTION(aLength <= kMaxLength, "excessive length for gfxShapedWord!");
+
+        // In the 16-bit version of Create, if the TEXT_IS_8BIT flag is set,
+        // then we convert the text to an 8-bit version and call the 8-bit
+        // Create function instead.
+        if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) {
+            nsCAutoString narrowText;
+            LossyAppendUTF16toASCII(nsDependentSubstring(aText, aLength),
+                                    narrowText);
+            return Create((const PRUint8*)(narrowText.BeginReading()),
+                          aLength, aRunScript, aAppUnitsPerDevUnit, aFlags);
+        }
+
+        PRUint32 size =
+            offsetof(gfxShapedWord, mCharacterGlyphs) +
+            aLength * (sizeof(CompressedGlyph) + sizeof(PRUnichar));
+        void *storage = moz_malloc(size);
+        if (!storage) {
+            return nsnull;
+        }
+
+        return new (storage) gfxShapedWord(aText, aLength, aRunScript,
+                                           aAppUnitsPerDevUnit, aFlags);
+    }
+
+    // Override operator delete to properly free the object that was
+    // allocated via moz_malloc.
+    void operator delete(void* p) {
+        moz_free(p);
+    }
+
+    /**
+     * 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 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 12) (specified
+            // in appunits).
+            FLAG_IS_SIMPLE_GLYPH  = 0x80000000U,
+
+            // Indicates whether a linebreak is allowed before this character;
+            // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value
+            // indicating the kind of linebreak (if any) allowed here.
+            FLAGS_CAN_BREAK_BEFORE = 0x60000000U,
+
+            FLAGS_CAN_BREAK_SHIFT = 29,
+            FLAG_BREAK_TYPE_NONE   = 0,
+            FLAG_BREAK_TYPE_NORMAL = 1,
+            FLAG_BREAK_TYPE_HYPHEN = 2,
+
+            FLAG_CHAR_IS_SPACE     = 0x10000000U,
+
+            // The advance is stored in appunits
+            ADVANCE_MASK  = 0x0FFF0000U,
+            ADVANCE_SHIFT = 16,
+
+            GLYPH_MASK = 0x0000FFFFU,
+
+            // 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_CHAR_IS_TAB              = 0x08,
+            FLAG_CHAR_IS_NEWLINE          = 0x10,
+            FLAG_CHAR_IS_LOW_SURROGATE    = 0x20,
+            
+            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
+        static bool IsSimpleGlyphID(PRUint32 aGlyph) {
+            return (aGlyph & GLYPH_MASK) == aGlyph;
+        }
+        // Returns true if the advance aAdvance fits into the compressed representation.
+        // aAdvance is in appunits.
+        static bool IsSimpleAdvance(PRUint32 aAdvance) {
+            return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance;
+        }
+
+        bool IsSimpleGlyph() const { return (mValue & FLAG_IS_SIMPLE_GLYPH) != 0; }
+        PRUint32 GetSimpleAdvance() const { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; }
+        PRUint32 GetSimpleGlyph() const { return mValue & GLYPH_MASK; }
+
+        bool IsMissing() const { return (mValue & (FLAG_NOT_MISSING|FLAG_IS_SIMPLE_GLYPH)) == 0; }
+        bool IsClusterStart() const {
+            return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_CLUSTER_START);
+        }
+        bool IsLigatureGroupStart() const {
+            return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_LIGATURE_GROUP_START);
+        }
+        bool IsLigatureContinuation() const {
+            return (mValue & FLAG_IS_SIMPLE_GLYPH) == 0 &&
+                (mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) ==
+                    (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING);
+        }
+
+        // Return true if the original character was a normal (breakable,
+        // trimmable) space (U+0020). Not true for other characters that
+        // may happen to map to the space glyph (U+00A0).
+        bool CharIsSpace() const {
+            return (mValue & FLAG_CHAR_IS_SPACE) != 0;
+        }
+
+        bool CharIsTab() const {
+            return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_TAB) != 0;
+        }
+        bool CharIsNewline() const {
+            return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE) != 0;
+        }
+        bool CharIsLowSurrogate() const {
+            return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_LOW_SURROGATE) != 0;
+        }
+
+        void SetClusterStart(bool aIsClusterStart) {
+            NS_ASSERTION(!IsSimpleGlyph(),
+                         "can't call SetClusterStart on simple glyphs");
+            if (aIsClusterStart) {
+                mValue &= ~FLAG_NOT_CLUSTER_START;
+            } else {
+                mValue |= FLAG_NOT_CLUSTER_START;
+            }
+        }
+
+        PRUint8 CanBreakBefore() const {
+            return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT;
+        }
+        // Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise
+        PRUint32 SetCanBreakBefore(PRUint8 aCanBreakBefore) {
+            NS_ASSERTION(aCanBreakBefore <= 2,
+                         "Bogus break-before value!");
+            PRUint32 breakMask = (PRUint32(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT);
+            PRUint32 toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE);
+            mValue ^= toggle;
+            return toggle;
+        }
+
+        CompressedGlyph& SetSimpleGlyph(PRUint32 aAdvanceAppUnits, PRUint32 aGlyph) {
+            NS_ASSERTION(IsSimpleAdvance(aAdvanceAppUnits), "Advance overflow");
+            NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow");
+            mValue = (mValue & FLAGS_CAN_BREAK_BEFORE) |
+                FLAG_IS_SIMPLE_GLYPH |
+                (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph;
+            return *this;
+        }
+        CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart,
+                PRUint32 aGlyphCount) {
+            mValue = (mValue & FLAGS_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;
+        }
         /**
-         * When set, GetHyphenationBreaks may return true for some character
-         * positions, otherwise it will always return false for all characters.
-         */
-        TEXT_ENABLE_HYPHEN_BREAKS    = 0x0010,
-        /**
-         * When set, the text has no characters above 255 and it is stored
-         * in the textrun in 8-bit format.
-         */
-        TEXT_IS_8BIT                 = 0x0020,
-        /**
-         * When set, the RunMetrics::mBoundingBox field will be initialized
-         * properly based on glyph extents, in particular, glyph extents that
-         * overflow the standard font-box (the box defined by the ascent, descent
-         * and advance width of the glyph). When not set, it may just be the
-         * standard font-box even if glyphs overflow.
+         * Missing glyphs are treated as ligature group starts; don't mess with
+         * the cluster-start flag (see bugs 618870 and 619286).
          */
-        TEXT_NEED_BOUNDING_BOX       = 0x0040,
-        /**
-         * When set, optional ligatures are disabled. Ligatures that are
-         * required for legible text should still be enabled.
-         */
-        TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0080,
-        /**
-         * When set, the textrun should favour speed of construction over
-         * quality. This may involve disabling ligatures and/or kerning or
-         * other effects.
-         */
-        TEXT_OPTIMIZE_SPEED          = 0x0100,
-        /**
-         * For internal use by the memory reporter when accounting for
-         * storage used by textruns.
-         * Because the reporter may visit each textrun multiple times while
-         * walking the frame trees and textrun cache, it needs to mark
-         * textruns that have been seen so as to avoid multiple-accounting.
-         */
-        TEXT_RUN_SIZE_ACCOUNTED      = 0x0200
+        CompressedGlyph& SetMissing(PRUint32 aGlyphCount) {
+            mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_NOT_CLUSTER_START)) |
+                (aGlyphCount << GLYPH_COUNT_SHIFT);
+            return *this;
+        }
+        PRUint32 GetGlyphCount() const {
+            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+            return (mValue & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT;
+        }
+
+        void SetIsSpace() {
+            mValue |= FLAG_CHAR_IS_SPACE;
+        }
+        void SetIsTab() {
+            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+            mValue |= FLAG_CHAR_IS_TAB;
+        }
+        void SetIsNewline() {
+            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+            mValue |= FLAG_CHAR_IS_NEWLINE;
+        }
+        void SetIsLowSurrogate() {
+            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+            mValue |= FLAG_CHAR_IS_LOW_SURROGATE;
+        }
+
+    private:
+        PRUint32 mValue;
     };
 
     /**
-     * This record contains all the parameters needed to initialize a textrun.
+     * When the glyphs for a character don't fit into a CompressedGlyph record
+     * in SimpleGlyph format, we use an array of DetailedGlyphs instead.
      */
-    struct Parameters {
-        // A reference context suggesting where the textrun will be rendered
-        gfxContext   *mContext;
-        // Pointer to arbitrary user data (which should outlive the textrun)
-        void         *mUserData;
-        // A description of which characters have been stripped from the original
-        // DOM string to produce the characters in the textrun. May be null
-        // if that information is not relevant.
-        gfxSkipChars *mSkipChars;
-        // A list of where linebreaks are currently placed in the textrun. May
-        // be null if mInitialBreakCount is zero.
-        PRUint32     *mInitialBreaks;
-        PRUint32      mInitialBreakCount;
-        // The ratio to use to convert device pixels to application layout units
-        PRUint32      mAppUnitsPerDevUnit;
+    struct DetailedGlyph {
+        /** 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 top to bottom */   
+        PRInt32  mAdvance;
+        float    mXOffset, mYOffset;
     };
 
-    virtual ~gfxTextRunFactory() {}
+    bool IsClusterStart(PRUint32 aPos) {
+        NS_ASSERTION(aPos < Length(), "aPos out of range");
+        return mCharacterGlyphs[aPos].IsClusterStart();
+    }
+
+    bool IsLigatureGroupStart(PRUint32 aPos) {
+        NS_ASSERTION(aPos < Length(), "aPos out of range");
+        return mCharacterGlyphs[aPos].IsLigatureGroupStart();
+    }
+
+    PRUint32 Length() const {
+        return mLength;
+    }
+
+    const PRUint8* Text8Bit() const {
+        NS_ASSERTION(TextIs8Bit(), "invalid use of Text8Bit()");
+        return reinterpret_cast<const PRUint8*>(&mCharacterGlyphs[Length()]);
+    }
+
+    const PRUnichar* TextUnicode() const {
+        NS_ASSERTION(!TextIs8Bit(), "invalid use of TextUnicode()");
+        return reinterpret_cast<const PRUnichar*>(&mCharacterGlyphs[Length()]);
+    }
+
+    PRUnichar GetCharAt(PRUint32 aOffset) const {
+        NS_ASSERTION(aOffset < Length(), "aOffset out of range");
+        return TextIs8Bit() ?
+            PRUnichar(Text8Bit()[aOffset]) : TextUnicode()[aOffset];
+    }
+
+    PRUint32 Flags() const {
+        return mFlags;
+    }
+
+    bool IsRightToLeft() const {
+        return (Flags() & gfxTextRunFactory::TEXT_IS_RTL) != 0;
+    }
+
+    float GetDirection() const {
+        return IsRightToLeft() ? -1.0 : 1.0;
+    }
+
+    bool DisableLigatures() const {
+        return (Flags() & gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0;
+    }
+
+    bool TextIs8Bit() const {
+        return (Flags() & gfxTextRunFactory::TEXT_IS_8BIT) != 0;
+    }
+
+    PRInt32 Script() const {
+        return mScript;
+    }
+
+    PRInt32 AppUnitsPerDevUnit() const {
+        return mAppUnitsPerDevUnit;
+    }
+
+    void ResetAge() {
+        mAgeCounter = 0;
+    }
+    PRUint32 IncrementAge() {
+        return ++mAgeCounter;
+    }
+
+    void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
+        NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here");
+        NS_ASSERTION(mCharacterGlyphs, "mCharacterGlyphs pointer is null!");
+        mCharacterGlyphs[aCharIndex] = aGlyph;
+    }
+
+    void SetGlyphs(PRUint32 aCharIndex, CompressedGlyph aGlyph,
+                   const DetailedGlyph *aGlyphs);
+
+    void SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar, gfxFont *aFont);
+
+    void SetIsSpace(PRUint32 aIndex) {
+        mCharacterGlyphs[aIndex].SetIsSpace();
+    }
+
+    void SetIsLowSurrogate(PRUint32 aIndex) {
+        SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nsnull);
+        mCharacterGlyphs[aIndex].SetIsLowSurrogate();
+    }
+
+    bool FilterIfIgnorable(PRUint32 aIndex);
+
+    const CompressedGlyph *GetCharacterGlyphs() const {
+        return &mCharacterGlyphs[0];
+    }
+
+    bool HasDetailedGlyphs() const {
+        return mDetailedGlyphs != nsnull;
+    }
+
+    // NOTE that this must not be called for a character offset that does
+    // not have any DetailedGlyph records; callers must have verified that
+    // mCharacterGlyphs[aCharIndex].GetGlyphCount() is greater than zero.
+    DetailedGlyph *GetDetailedGlyphs(PRUint32 aCharIndex) const {
+        NS_ASSERTION(HasDetailedGlyphs() &&
+                     !mCharacterGlyphs[aCharIndex].IsSimpleGlyph() &&
+                     mCharacterGlyphs[aCharIndex].GetGlyphCount() > 0,
+                     "invalid use of GetDetailedGlyphs; check the caller!");
+        return mDetailedGlyphs->Get(aCharIndex);
+    }
+
+    void AdjustAdvancesForSyntheticBold(float aSynBoldOffset);
+
+    // this is a public static method in order to make it available
+    // for gfxTextRun to use directly on its own CompressedGlyph array,
+    // in addition to the use within ShapedWord
+    static void
+    SetupClusterBoundaries(CompressedGlyph *aGlyphs,
+                           const PRUnichar *aString, PRUint32 aLength);
+
+private:
+    // so that gfxTextRun can share our DetailedGlyphStore class
+    friend class gfxTextRun;
+
+    // Construct storage for a ShapedWord, ready to receive glyph data
+    gfxShapedWord(const PRUint8 *aText, PRUint32 aLength,
+                  PRInt32 aRunScript, PRInt32 aAppUnitsPerDevUnit,
+                  PRUint32 aFlags)
+        : mLength(aLength)
+        , mFlags(aFlags | gfxTextRunFactory::TEXT_IS_8BIT)
+        , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit)
+        , mScript(aRunScript)
+        , mAgeCounter(0)
+    {
+        memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph));
+        PRUint8 *text = reinterpret_cast<PRUint8*>(&mCharacterGlyphs[aLength]);
+        memcpy(text, aText, aLength * sizeof(PRUint8));
+    }
+
+    gfxShapedWord(const PRUnichar *aText, PRUint32 aLength,
+                  PRInt32 aRunScript, PRInt32 aAppUnitsPerDevUnit,
+                  PRUint32 aFlags)
+        : mLength(aLength)
+        , mFlags(aFlags)
+        , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit)
+        , mScript(aRunScript)
+        , mAgeCounter(0)
+    {
+        memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph));
+        PRUnichar *text = reinterpret_cast<PRUnichar*>(&mCharacterGlyphs[aLength]);
+        memcpy(text, aText, aLength * sizeof(PRUnichar));
+        SetupClusterBoundaries(&mCharacterGlyphs[0], aText, aLength);
+    }
+
+    // Allocate aCount DetailedGlyphs for the given index
+    DetailedGlyph *AllocateDetailedGlyphs(PRUint32 aCharIndex,
+                                          PRUint32 aCount);
+
+    // For characters whose glyph data does not fit the "simple" glyph criteria
+    // in CompressedGlyph, we use a sorted array to store the association
+    // between the source character offset and an index into an array 
+    // DetailedGlyphs. The CompressedGlyph record includes a count of
+    // the number of DetailedGlyph records that belong to the character,
+    // starting at the given index.
+    class DetailedGlyphStore {
+    public:
+        DetailedGlyphStore()
+            : mLastUsed(0)
+        { }
+
+        // This is optimized for the most common calling patterns:
+        // we rarely need random access to the records, access is most commonly
+        // sequential through the textRun, so we record the last-used index
+        // and check whether the caller wants the same record again, or the
+        // next; if not, it's most likely we're starting over from the start
+        // of the run, so we check the first entry before resorting to binary
+        // search as a last resort.
+        // NOTE that this must not be called for a character offset that does
+        // not have any DetailedGlyph records; callers must have verified that
+        // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero
+        // before calling this, otherwise the assertions here will fire (in a
+        // debug build), and we'll probably crash.
+        DetailedGlyph* Get(PRUint32 aOffset) {
+            NS_ASSERTION(mOffsetToIndex.Length() > 0,
+                         "no detailed glyph records!");
+            DetailedGlyph* details = mDetails.Elements();
+            // check common cases (fwd iteration, initial entry, etc) first
+            if (mLastUsed < mOffsetToIndex.Length() - 1 &&
+                aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) {
+                ++mLastUsed;
+            } else if (aOffset == mOffsetToIndex[0].mOffset) {
+                mLastUsed = 0;
+            } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) {
+                // do nothing
+            } else if (mLastUsed > 0 &&
+                       aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) {
+                --mLastUsed;
+            } else {
+                mLastUsed =
+                    mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset());
+            }
+            NS_ASSERTION(mLastUsed != nsTArray<DGRec>::NoIndex,
+                         "detailed glyph record missing!");
+            return details + mOffsetToIndex[mLastUsed].mIndex;
+        }
+
+        DetailedGlyph* Allocate(PRUint32 aOffset, PRUint32 aCount) {
+            PRUint32 detailIndex = mDetails.Length();
+            DetailedGlyph *details = mDetails.AppendElements(aCount);
+            if (!details) {
+                return nsnull;
+            }
+            // We normally set up glyph records sequentially, so the common case
+            // here is to append new records to the mOffsetToIndex array;
+            // test for that before falling back to the InsertElementSorted
+            // method.
+            if (mOffsetToIndex.Length() == 0 ||
+                aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) {
+                if (!mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex))) {
+                    return nsnull;
+                }
+            } else {
+                if (!mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex),
+                                                        CompareRecordOffsets())) {
+                    return nsnull;
+                }
+            }
+            return details;
+        }
+
+        size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) {
+            return aMallocSizeOf(this, sizeof(DetailedGlyphStore)) +
+                mDetails.SizeOfExcludingThis(aMallocSizeOf) +
+                mOffsetToIndex.SizeOfExcludingThis(aMallocSizeOf);
+        }
+
+    private:
+        struct DGRec {
+            DGRec(const PRUint32& aOffset, const PRUint32& aIndex)
+                : mOffset(aOffset), mIndex(aIndex) { }
+            PRUint32 mOffset; // source character offset in the textrun
+            PRUint32 mIndex;  // index where this char's DetailedGlyphs begin
+        };
+
+        struct CompareToOffset {
+            bool Equals(const DGRec& a, const PRUint32& b) const {
+                return a.mOffset == b;
+            }
+            bool LessThan(const DGRec& a, const PRUint32& b) const {
+                return a.mOffset < b;
+            }
+        };
+
+        struct CompareRecordOffsets {
+            bool Equals(const DGRec& a, const DGRec& b) const {
+                return a.mOffset == b.mOffset;
+            }
+            bool LessThan(const DGRec& a, const DGRec& b) const {
+                return a.mOffset < b.mOffset;
+            }
+        };
+
+        // Concatenated array of all the DetailedGlyph records needed for the
+        // textRun; individual character offsets are associated with indexes
+        // into this array via the mOffsetToIndex table.
+        nsTArray<DetailedGlyph>     mDetails;
+
+        // For each character offset that needs DetailedGlyphs, we record the
+        // index in mDetails where the list of glyphs begins. This array is
+        // sorted by mOffset.
+        nsTArray<DGRec>             mOffsetToIndex;
+
+        // Records the most recently used index into mOffsetToIndex, so that
+        // we can support sequential access more quickly than just doing
+        // a binary search each time.
+        nsTArray<DGRec>::index_type mLastUsed;
+    };
+
+    nsAutoPtr<DetailedGlyphStore>   mDetailedGlyphs;
+
+    // Number of PRUnichar characters and CompressedGlyph glyph records;
+    // note that gfx font code will never attempt to create a ShapedWord
+    // with a huge number of characters, so we could limit this to 16 bits
+    // to minimize memory usage for large numbers of cached words.
+    PRUint32                        mLength;
+
+    PRUint32                        mFlags;
+
+    PRInt32                         mAppUnitsPerDevUnit;
+    PRInt32                         mScript;
+
+    PRUint32                        mAgeCounter;
+
+    // The mCharacterGlyphs array is actually a variable-size member;
+    // when the ShapedWord is created, its size will be increased as necessary
+    // to allow the proper number of glyphs to be stored.
+    // The original text, in either 8-bit or 16-bit form, will be stored
+    // immediately following the CompressedGlyphs.
+    CompressedGlyph                 mCharacterGlyphs[1];
 };
 
 /**
  * gfxTextRun is an abstraction for drawing and measuring substrings of a run
  * of text. It stores runs of positioned glyph data, each run having a single
  * gfxFont. The glyphs are associated with a string of source text, and the
  * gfxTextRun APIs take parameters that are offsets into that source text.
  * 
@@ -1438,36 +2162,31 @@ public:
  * lazily by methods that need it, it is not cached. Line breaks are stored
  * persistently (insofar as they affect the shaping of glyphs; gfxTextRun does
  * not actually do anything to explicitly account for line breaks). Initially
  * there are no line breaks. The textrun can record line breaks before or after
  * any given cluster. (Line breaks specified inside clusters are ignored.)
  * 
  * It is important that zero-length substrings are handled correctly. This will
  * be on the test!
- * 
- * gfxTextRun stores a list of zero or more glyphs for each character. For each
- * glyph we store the glyph ID, the advance, and possibly an xoffset and yoffset.
- * The idea is that a string is rendered by a loop that draws each glyph
- * at its designated offset from the current point, then advances the current
- * point by the glyph's advance in the direction of the textrun (LTR or RTL).
- * Each glyph advance is always rounded to the nearest appunit; this ensures
- * consistent results when dividing the text in a textrun into multiple text
- * frames (frame boundaries are always aligned to appunits). We optimize
- * for the case where a character has a single glyph and zero xoffset and yoffset,
- * and the glyph ID and advance are in a reasonable range so we can pack all
- * necessary data into 32 bits.
- * 
- * gfxTextRun methods that measure or draw substrings will associate all the
- * glyphs in a cluster with the first character of the cluster; if that character
- * is in the substring, the glyphs will be measured or drawn, otherwise they
- * won't.
  */
 class THEBES_API gfxTextRun {
 public:
+    // we use the same glyph storage as gfxShapedWord, to facilitate copying
+    // glyph data from shaped words into text runs as needed
+    typedef gfxShapedWord::CompressedGlyph    CompressedGlyph;
+    typedef gfxShapedWord::DetailedGlyph      DetailedGlyph;
+    typedef gfxShapedWord::DetailedGlyphStore DetailedGlyphStore;
+
+    // Override operator delete to properly free the object that was
+    // allocated via moz_malloc.
+    void operator delete(void* p) {
+        moz_free(p);
+    }
+
     virtual ~gfxTextRun();
 
     typedef gfxFont::RunMetrics Metrics;
 
     // Public textrun API for general use
 
     bool IsClusterStart(PRUint32 aPos) {
         NS_ASSERTION(aPos < mCharacterCount, "aPos out of range");
@@ -1483,16 +2202,33 @@ public:
             CompressedGlyph::FLAG_BREAK_TYPE_NORMAL;
     }
     bool CanHyphenateBefore(PRUint32 aPos) {
         NS_ASSERTION(aPos < mCharacterCount, "aPos out of range");
         return mCharacterGlyphs[aPos].CanBreakBefore() ==
             CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN;
     }
 
+    bool CharIsSpace(PRUint32 aPos) {
+        NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
+        return mCharacterGlyphs[aPos].CharIsSpace();
+    }
+    bool CharIsTab(PRUint32 aPos) {
+        NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
+        return mCharacterGlyphs[aPos].CharIsTab();
+    }
+    bool CharIsNewline(PRUint32 aPos) {
+        NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
+        return mCharacterGlyphs[aPos].CharIsNewline();
+    }
+    bool CharIsLowSurrogate(PRUint32 aPos) {
+        NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
+        return mCharacterGlyphs[aPos].CharIsLowSurrogate();
+    }
+
     PRUint32 GetLength() { return mCharacterCount; }
 
     // All PRUint32 aStart, PRUint32 aLength ranges below are restricted to
     // grapheme cluster boundaries! All offsets are in terms of the string
     // passed into MakeTextRun.
     
     // All coordinates are in layout/app units
 
@@ -1758,191 +2494,23 @@ public:
     void ClearFlagBits(PRUint32 aFlags) {
       NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS),
                    "Only user flags should be mutable");
       mFlags &= ~aFlags;
     }
     const gfxSkipChars& GetSkipChars() const { return mSkipChars; }
     PRUint32 GetAppUnitsPerDevUnit() const { return mAppUnitsPerDevUnit; }
     gfxFontGroup *GetFontGroup() const { return mFontGroup; }
-    const PRUint8 *GetText8Bit() const
-    { return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? mText.mSingle : nsnull; }
-    const PRUnichar *GetTextUnicode() const
-    { return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? nsnull : mText.mDouble; }
-    const void *GetTextAt(PRUint32 aIndex) {
-        return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT)
-            ? static_cast<const void *>(mText.mSingle + aIndex)
-            : static_cast<const void *>(mText.mDouble + aIndex);
-    }
-    const PRUnichar GetChar(PRUint32 i) const
-    { return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? mText.mSingle[i] : mText.mDouble[i]; }
-    PRUint32 GetHashCode() const { return mHashCode; }
-    void SetHashCode(PRUint32 aHash) { mHashCode = aHash; }
+
 
     // Call this, don't call "new gfxTextRun" directly. This does custom
     // allocation and initialization
     static gfxTextRun *Create(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 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 13) (specified
-            // in appunits).
-            FLAG_IS_SIMPLE_GLYPH  = 0x80000000U,
-
-            // Indicates whether a linebreak is allowed before this character;
-            // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value
-            // indicating the kind of linebreak (if any) allowed here.
-            FLAGS_CAN_BREAK_BEFORE = 0x60000000U,
-
-            FLAGS_CAN_BREAK_SHIFT = 29,
-            FLAG_BREAK_TYPE_NONE   = 0,
-            FLAG_BREAK_TYPE_NORMAL = 1,
-            FLAG_BREAK_TYPE_HYPHEN = 2,
-
-            // The advance is stored in appunits
-            ADVANCE_MASK  = 0x1FFF0000U,
-            ADVANCE_SHIFT = 16,
-
-            GLYPH_MASK = 0x0000FFFFU,
-
-            // 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,
-            
-            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
-        static bool IsSimpleGlyphID(PRUint32 aGlyph) {
-            return (aGlyph & GLYPH_MASK) == aGlyph;
-        }
-        // Returns true if the advance aAdvance fits into the compressed representation.
-        // aAdvance is in appunits.
-        static bool IsSimpleAdvance(PRUint32 aAdvance) {
-            return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance;
-        }
-
-        bool IsSimpleGlyph() const { return (mValue & FLAG_IS_SIMPLE_GLYPH) != 0; }
-        PRUint32 GetSimpleAdvance() const { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; }
-        PRUint32 GetSimpleGlyph() const { return mValue & GLYPH_MASK; }
-
-        bool IsMissing() const { return (mValue & (FLAG_NOT_MISSING|FLAG_IS_SIMPLE_GLYPH)) == 0; }
-        bool IsClusterStart() const {
-            return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_CLUSTER_START);
-        }
-        bool IsLigatureGroupStart() const {
-            return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_LIGATURE_GROUP_START);
-        }
-        bool IsLigatureContinuation() const {
-            return (mValue & FLAG_IS_SIMPLE_GLYPH) == 0 &&
-                (mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) ==
-                    (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING);
-        }
-
-        PRUint8 CanBreakBefore() const {
-            return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT;
-        }
-        // Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise
-        PRUint32 SetCanBreakBefore(PRUint8 aCanBreakBefore) {
-            NS_ASSERTION(aCanBreakBefore <= 2,
-                         "Bogus break-before value!");
-            PRUint32 breakMask = (PRUint32(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT);
-            PRUint32 toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE);
-            mValue ^= toggle;
-            return toggle;
-        }
-
-        CompressedGlyph& SetSimpleGlyph(PRUint32 aAdvanceAppUnits, PRUint32 aGlyph) {
-            NS_ASSERTION(IsSimpleAdvance(aAdvanceAppUnits), "Advance overflow");
-            NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow");
-            mValue = (mValue & FLAGS_CAN_BREAK_BEFORE) |
-                FLAG_IS_SIMPLE_GLYPH |
-                (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph;
-            return *this;
-        }
-        CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart,
-                PRUint32 aGlyphCount) {
-            mValue = (mValue & FLAGS_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;
-        }
-        /**
-         * Missing glyphs are treated as ligature group starts; don't mess with
-         * the cluster-start flag (see bugs 618870 and 619286).
-         */
-        CompressedGlyph& SetMissing(PRUint32 aGlyphCount) {
-            mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_NOT_CLUSTER_START)) |
-                (aGlyphCount << GLYPH_COUNT_SHIFT);
-            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 {
-        /** 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 top to bottom */   
-        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
         PRUint8           mMatchType;
     };
 
     class THEBES_API GlyphRunIterator {
@@ -2001,47 +2569,90 @@ public:
     nsresult AddGlyphRun(gfxFont *aFont, PRUint8 aMatchType,
                          PRUint32 aStartCharIndex, bool aForceNewRun);
     void ResetGlyphRuns() { mGlyphRuns.Clear(); }
     void SortGlyphRuns();
     void SanitizeGlyphRuns();
 
     // Call the following glyph-setters during initialization or during reshaping
     // only. It is OK to overwrite existing data for a character.
+    void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
+        NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here");
+        mCharacterGlyphs[aCharIndex] = aGlyph;
+    }
     /**
      * 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 SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
-        NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here");
-        if (mCharacterGlyphs) {
-            mCharacterGlyphs[aCharIndex] = aGlyph;
-        }
-    }
     void SetGlyphs(PRUint32 aCharIndex, CompressedGlyph aGlyph,
                    const DetailedGlyph *aGlyphs);
     void SetMissingGlyph(PRUint32 aCharIndex, PRUint32 aUnicodeChar);
     void SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, PRUint32 aCharIndex);
 
-    // If the character at aIndex is default-ignorable, set the glyph
-    // to be invisible-missing and return TRUE, else return FALSE
-    bool FilterIfIgnorable(PRUint32 aIndex);
+    // Set the glyph data for the given character index to the font's
+    // space glyph, IF this can be done as a "simple" glyph record
+    // (not requiring a DetailedGlyph entry). This avoids the need to call
+    // the font shaper and go through the shaped-word cache for most spaces.
+    //
+    // The parameter aSpaceChar is the original character code for which
+    // this space glyph is being used; if this is U+0020, we need to record
+    // that it could be trimmed at a run edge, whereas other kinds of space
+    // (currently just U+00A0) would not be trimmable/breakable.
+    //
+    // Returns true if it was able to set simple glyph data for the space;
+    // if it returns false, the caller needs to fall back to some other
+    // means to create the necessary (detailed) glyph data.
+    bool SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext,
+                               PRUint32 aCharIndex, PRUnichar aSpaceChar);
+
+    // Record the positions of specific characters that layout may need to
+    // detect in the textrun, even though it doesn't have an explicit copy
+    // of the original text. These are recorded using flag bits in the
+    // CompressedGlyph record; if necessary, we convert "simple" glyph records
+    // to "complex" ones as the Tab and Newline flags are not present in
+    // simple CompressedGlyph records.
+    void SetIsTab(PRUint32 aIndex) {
+        CompressedGlyph *g = &mCharacterGlyphs[aIndex];
+        if (g->IsSimpleGlyph()) {
+            DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+            details->mGlyphID = g->GetSimpleGlyph();
+            details->mAdvance = g->GetSimpleAdvance();
+            details->mXOffset = details->mYOffset = 0;
+            SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details);
+        }
+        g->SetIsTab();
+    }
+    void SetIsNewline(PRUint32 aIndex) {
+        CompressedGlyph *g = &mCharacterGlyphs[aIndex];
+        if (g->IsSimpleGlyph()) {
+            DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+            details->mGlyphID = g->GetSimpleGlyph();
+            details->mAdvance = g->GetSimpleAdvance();
+            details->mXOffset = details->mYOffset = 0;
+            SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details);
+        }
+        g->SetIsNewline();
+    }
+    void SetIsLowSurrogate(PRUint32 aIndex) {
+        SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nsnull);
+        mCharacterGlyphs[aIndex].SetIsLowSurrogate();
+    }
 
     /**
      * Prefetch all the glyph extents needed to ensure that Measure calls
      * on this textrun not requesting tight boundingBoxes will succeed. Note
      * that some glyph extents might not be fetched due to OOM or other
      * errors.
      */
     void FetchGlyphExtents(gfxContext *aRefContext);
 
     // API for access to the raw glyph data, needed by gfxFont::Draw
     // and gfxFont::GetBoundingBox
-    const CompressedGlyph *GetCharacterGlyphs() { return mCharacterGlyphs; }
+    CompressedGlyph *GetCharacterGlyphs() { return mCharacterGlyphs; }
 
     // NOTE that this must not be called for a character offset that does
     // not have any DetailedGlyph records; callers must have verified that
     // mCharacterGlyphs[aCharIndex].GetGlyphCount() is greater than zero.
     DetailedGlyph *GetDetailedGlyphs(PRUint32 aCharIndex) {
         NS_ASSERTION(mDetailedGlyphs != nsnull &&
                      !mCharacterGlyphs[aCharIndex].IsSimpleGlyph() &&
                      mCharacterGlyphs[aCharIndex].GetGlyphCount() > 0,
@@ -2053,20 +2664,24 @@ public:
     PRUint32 CountMissingGlyphs();
     const GlyphRun *GetGlyphRuns(PRUint32 *aNumGlyphRuns) {
         *aNumGlyphRuns = mGlyphRuns.Length();
         return mGlyphRuns.Elements();
     }
     // Returns the index of the GlyphRun containing the given offset.
     // Returns mGlyphRuns.Length() when aOffset is mCharacterCount.
     PRUint32 FindFirstGlyphRunContaining(PRUint32 aOffset);
+
+    // Copy glyph data from a ShapedWord into this textrun.
+    void CopyGlyphDataFrom(const gfxShapedWord *aSource, PRUint32 aStart);
+
     // Copy glyph data for a range of characters from aSource to this
     // textrun.
-    virtual void CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart,
-                                   PRUint32 aLength, PRUint32 aDest);
+    void CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart,
+                           PRUint32 aLength, PRUint32 aDest);
 
     nsExpirationState *GetExpirationState() { return &mExpirationState; }
 
     struct LigatureData {
         // textrun offsets of the start and end of the containing ligature
         PRUint32 mLigatureStart;
         PRUint32 mLigatureEnd;
         // appunits advance to the start of the ligature part within the ligature;
@@ -2076,19 +2691,16 @@ public:
         // when the part is at the start of the ligature, and after-spacing
         // when the part is as the end of the ligature
         gfxFloat mPartWidth;
         
         bool mClipBeforePart;
         bool mClipAfterPart;
     };
     
-    // user font set generation when text run was created
-    PRUint64 GetUserFontSetGeneration() { return mUserFontSetGeneration; }
-
     // return storage used by this run, for memory reporter;
     // nsTransformedTextRun needs to override this as it holds additional data
     virtual NS_MUST_OVERRIDE size_t
         SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf);
     virtual NS_MUST_OVERRIDE size_t
         SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf);
 
     // Get the size, if it hasn't already been gotten, marking as it goes.
@@ -2099,49 +2711,41 @@ public:
         mFlags |= gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED;
         return SizeOfIncludingThis(aMallocSizeOf);
     }
     void ResetSizeOfAccountingFlags() {
         mFlags &= ~gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED;
     }
 
 #ifdef DEBUG
-    // number of entries referencing this textrun in the gfxTextRunWordCache
-    PRUint32 mCachedWords;
-    // generation of gfxTextRunWordCache that refers to this textrun;
-    // if the cache gets cleared, then mCachedWords is no longer meaningful
-    PRUint32 mCacheGeneration;
-    
     void Dump(FILE* aOutput);
 #endif
 
-    // post-process glyph advances to deal with synthetic bolding
-    void AdjustAdvancesForSyntheticBold(gfxContext *aContext,
-                                        PRUint32 aStart,
-                                        PRUint32 aLength);
-
 protected:
     /**
-     * Initializes the textrun to blank.
-     * @param aGlyphStorage preallocated array of CompressedGlyph[aLength]
-     * for the textrun to use; if aText is not persistent, then it has also
-     * been appended to this array, so it must NOT be freed separately.
+     * Create a textrun, and set its mCharacterGlyphs to point immediately
+     * after the base object; this is ONLY used in conjunction with placement
+     * new, after allocating a block large enough for the glyph records to
+     * follow the base textrun object.
      */
     gfxTextRun(const gfxTextRunFactory::Parameters *aParams, const void *aText,
-               PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags,
-               CompressedGlyph *aGlyphStorage);
+               PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags);
 
     /**
      * Helper for the Create() factory method to allocate the required
-     * glyph storage, and copy the text (modifying the aText parameter)
-     * if it is not flagged as persistent.
+     * glyph storage for a textrun object with the basic size aSize,
+     * plus room for aLength glyph records.
      */
-    static CompressedGlyph* AllocateStorage(const void*& aText,
-                                            PRUint32 aLength,
-                                            PRUint32 aFlags);
+    static void* AllocateStorageForTextRun(size_t aSize, PRUint32 aLength);
+
+    // All our glyph data is in logical order, not visual.
+    // Space for mCharacterGlyphs is allocated fused with the textrun object,
+    // and then the constructor sets the pointer to the beginning of this
+    // storage area. Thus, this pointer must NOT be freed!
+    CompressedGlyph  *mCharacterGlyphs;
 
 private:
     // **** general helpers **** 
 
     // Allocate aCount DetailedGlyphs for the given index
     DetailedGlyph *AllocateDetailedGlyphs(PRUint32 aCharIndex, PRUint32 aCount);
 
     // Get the total advance for a range of glyphs.
@@ -2189,166 +2793,29 @@ private:
                                  Metrics *aMetrics);
 
     // **** drawing helper ****
     void DrawGlyphs(gfxFont *aFont, gfxContext *aContext, bool aDrawToPath,
                     gfxPoint *aPt, PRUint32 aStart, PRUint32 aEnd,
                     PropertyProvider *aProvider,
                     PRUint32 aSpacingStart, PRUint32 aSpacingEnd);
 
-    // All our glyph data is in logical order, not visual.
-    // mCharacterGlyphs is allocated by the factory that creates the textrun,
-    // to avoid the possibility of failure during the constructor;
-    // however, ownership passes to the textrun during construction and so
-    // it must be deleted in the destructor.
-    CompressedGlyph*                               mCharacterGlyphs;
-
-    // For characters whose glyph data does not fit the "simple" glyph criteria
-    // in CompressedGlyph, we use a sorted array to store the association
-    // between the source character offset and an index into an array 
-    // DetailedGlyphs. The CompressedGlyph record includes a count of
-    // the number of DetailedGlyph records that belong to the character,
-    // starting at the given index.
-    class DetailedGlyphStore {
-    public:
-        DetailedGlyphStore()
-            : mLastUsed(0)
-        { }
-
-        // This is optimized for the most common calling patterns:
-        // we rarely need random access to the records, access is most commonly
-        // sequential through the textRun, so we record the last-used index
-        // and check whether the caller wants the same record again, or the
-        // next; if not, it's most likely we're starting over from the start
-        // of the run, so we check the first entry before resorting to binary
-        // search as a last resort.
-        // NOTE that this must not be called for a character offset that does
-        // not have any DetailedGlyph records; callers must have verified that
-        // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero
-        // before calling this, otherwise the assertions here will fire (in a
-        // debug build), and we'll probably crash.
-        DetailedGlyph* Get(PRUint32 aOffset) {
-            NS_ASSERTION(mOffsetToIndex.Length() > 0,
-                         "no detailed glyph records!");
-            DetailedGlyph* details = mDetails.Elements();
-            // check common cases (fwd iteration, initial entry, etc) first
-            if (mLastUsed < mOffsetToIndex.Length() - 1 &&
-                aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) {
-                ++mLastUsed;
-            } else if (aOffset == mOffsetToIndex[0].mOffset) {
-                mLastUsed = 0;
-            } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) {
-                // do nothing
-            } else if (mLastUsed > 0 &&
-                       aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) {
-                --mLastUsed;
-            } else {
-                mLastUsed =
-                    mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset());
-            }
-            NS_ASSERTION(mLastUsed != nsTArray<DGRec>::NoIndex,
-                         "detailed glyph record missing!");
-            return details + mOffsetToIndex[mLastUsed].mIndex;
-        }
-
-        DetailedGlyph* Allocate(PRUint32 aOffset, PRUint32 aCount) {
-            PRUint32 detailIndex = mDetails.Length();
-            DetailedGlyph *details = mDetails.AppendElements(aCount);
-            if (!details) {
-                return nsnull;
-            }
-            // We normally set up glyph records sequentially, so the common case
-            // here is to append new records to the mOffsetToIndex array;
-            // test for that before falling back to the InsertElementSorted
-            // method.
-            if (mOffsetToIndex.Length() == 0 ||
-                aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) {
-                if (!mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex))) {
-                    return nsnull;
-                }
-            } else {
-                if (!mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex),
-                                                        CompareRecordOffsets())) {
-                    return nsnull;
-                }
-            }
-            return details;
-        }
-
-        size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) {
-            return aMallocSizeOf(this, sizeof(DetailedGlyphStore)) +
-                mDetails.SizeOfExcludingThis(aMallocSizeOf) +
-                mOffsetToIndex.SizeOfExcludingThis(aMallocSizeOf);
-        }
-
-    private:
-        struct DGRec {
-            DGRec(const PRUint32& aOffset, const PRUint32& aIndex)
-                : mOffset(aOffset), mIndex(aIndex) { }
-            PRUint32 mOffset; // source character offset in the textrun
-            PRUint32 mIndex;  // index where this char's DetailedGlyphs begin
-        };
-
-        struct CompareToOffset {
-            bool Equals(const DGRec& a, const PRUint32& b) const {
-                return a.mOffset == b;
-            }
-            bool LessThan(const DGRec& a, const PRUint32& b) const {
-                return a.mOffset < b;
-            }
-        };
-
-        struct CompareRecordOffsets {
-            bool Equals(const DGRec& a, const DGRec& b) const {
-                return a.mOffset == b.mOffset;
-            }
-            bool LessThan(const DGRec& a, const DGRec& b) const {
-                return a.mOffset < b.mOffset;
-            }
-        };
-
-        // Concatenated array of all the DetailedGlyph records needed for the
-        // textRun; individual character offsets are associated with indexes
-        // into this array via the mOffsetToIndex table.
-        nsTArray<DetailedGlyph>     mDetails;
-
-        // For each character offset that needs DetailedGlyphs, we record the
-        // index in mDetails where the list of glyphs begins. This array is
-        // sorted by mOffset.
-        nsTArray<DGRec>             mOffsetToIndex;
-
-        // Records the most recently used index into mOffsetToIndex, so that
-        // we can support sequential access more quickly than just doing
-        // a binary search each time.
-        nsTArray<DGRec>::index_type mLastUsed;
-    };
-
     nsAutoPtr<DetailedGlyphStore>   mDetailedGlyphs;
 
     // XXX this should be changed to a GlyphRun plus a maybe-null GlyphRun*,
     // for smaller size especially in the super-common one-glyphrun case
     nsAutoTArray<GlyphRun,1>        mGlyphRuns;
-    // When TEXT_IS_8BIT is set, we use mSingle, otherwise we use mDouble.
-    // When TEXT_IS_PERSISTENT is set, we don't own the text, otherwise we
-    // own the text. When we own the text, it's allocated fused with the
-    // mCharacterGlyphs array, and therefore need not be explicitly deleted.
-    // This text is not null-terminated.
-    union {
-        const PRUint8   *mSingle;
-        const PRUnichar *mDouble;
-    } mText;
+
     void             *mUserData;
     gfxFontGroup     *mFontGroup; // addrefed
     gfxSkipChars      mSkipChars;
     nsExpirationState mExpirationState;
     PRUint32          mAppUnitsPerDevUnit;
     PRUint32          mFlags;
     PRUint32          mCharacterCount;
-    PRUint32          mHashCode;
-    PRUint64          mUserFontSetGeneration; // user font set generation when text run created
 
     bool              mSkipDrawing; // true if the font group we used had a user font
                                     // download that's in progress, so we should hide text
                                     // until the download completes (or timeout fires)
 };
 
 class THEBES_API gfxFontGroup : public gfxTextRunFactory {
 public:
@@ -2380,31 +2847,21 @@ public:
             mStyle.Equals(other.mStyle);
     }
 
     const gfxFontStyle *GetStyle() const { return &mStyle; }
 
     virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle);
 
     /**
-     * The listed characters should not be passed in to MakeTextRun and should
-     * be treated as invisible and zero-width.
+     * The listed characters should be treated as invisible and zero-width
+     * when creating textruns.
      */
+    static bool IsInvalidChar(PRUint8 ch);
     static bool IsInvalidChar(PRUnichar ch);
-    
-    /**
-     * Make a textrun for an empty string. This is fast; if you call it,
-     * don't bother caching the result.
-     */
-    gfxTextRun *MakeEmptyTextRun(const Parameters *aParams, PRUint32 aFlags);
-    /**
-     * Make a textrun for a single ASCII space. This is fast; if you call it,
-     * don't bother caching the result.
-     */
-    gfxTextRun *MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags);
 
     /**
      * Make a textrun for a given string.
      * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the
      * textrun will copy it.
      * This calls FetchGlyphExtents on the textrun.
      */
     virtual gfxTextRun *MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
@@ -2413,16 +2870,32 @@ public:
      * Make a textrun for a given string.
      * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the
      * textrun will copy it.
      * This calls FetchGlyphExtents on the textrun.
      */
     virtual gfxTextRun *MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
                                     const Parameters *aParams, PRUint32 aFlags);
 
+    /**
+     * Textrun creation helper for clients that don't want to pass
+     * a full Parameters record.
+     */
+    template<typename T>
+    gfxTextRun *MakeTextRun(const T *aString, PRUint32 aLength,
+                            gfxContext *aRefContext,
+                            PRUint32 aAppUnitsPerDevUnit,
+                            PRUint32 aFlags)
+    {
+        gfxTextRunFactory::Parameters params = {
+            aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit
+        };
+        return MakeTextRun(aString, aLength, &params, aFlags);
+    }
+
     /* helper function for splitting font families on commas and
      * calling a function for each family to fill the mFonts array
      */
     typedef bool (*FontCreationCallback) (const nsAString& aName,
                                             const nsACString& aGenericName,
                                             bool aUseFontSet,
                                             void *closure);
     bool ForEachFont(const nsAString& aFamilies,
@@ -2456,18 +2929,19 @@ public:
                         gfxFont *aPrevMatchedFont,
                         PRUint8 *aMatchType);
 
     // search through pref fonts for a character, return nsnull if no matching pref font
     virtual already_AddRefed<gfxFont> WhichPrefFontSupportsChar(PRUint32 aCh);
 
     virtual already_AddRefed<gfxFont> WhichSystemFontSupportsChar(PRUint32 aCh);
 
+    template<typename T>
     void ComputeRanges(nsTArray<gfxTextRange>& mRanges,
-                       const PRUnichar *aString, PRUint32 begin, PRUint32 end,
+                       const T *aString, PRUint32 aLength,
                        PRInt32 aRunScript);
 
     gfxUserFontSet* GetUserFontSet();
 
     // With downloadable fonts, the composition of the font group can change as fonts are downloaded
     // for each change in state of the user font set, the generation value is bumped to avoid picking up
     // previously created text runs in the text run word cache.  For font groups based on stylesheets
     // with no @font-face rule, this always returns 0.
@@ -2496,41 +2970,51 @@ protected:
     eFontPrefLang           mLastPrefLang;       // lang group for last pref font
     eFontPrefLang           mPageLang;
     bool                    mLastPrefFirstFont;  // is this the first font in the list of pref fonts for this lang group?
 
     bool                    mSkipDrawing; // hide text while waiting for a font
                                           // download to complete (or fallback
                                           // timer to fire)
 
+    /**
+     * Textrun creation short-cuts for special cases where we don't need to
+     * call a font shaper to generate glyphs.
+     */
+    gfxTextRun *MakeEmptyTextRun(const Parameters *aParams, PRUint32 aFlags);
+    gfxTextRun *MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags);
+    gfxTextRun *MakeBlankTextRun(const void* aText, PRUint32 aLength,
+                                 const Parameters *aParams, PRUint32 aFlags);
+
     // Used for construction/destruction.  Not intended to change the font set
     // as invalidation of font lists and caches is not considered.
     void SetUserFontSet(gfxUserFontSet *aUserFontSet);
 
     // Initialize the list of fonts
     void BuildFontList();
 
     // Init this font group's font metrics. If there no bad fonts, you don't need to call this.
     // But if there are one or more bad fonts which have bad underline offset,
     // you should call this with the *first* bad font.
     void InitMetricsForBadFont(gfxFont* aBadFont);
 
     // Set up the textrun glyphs for an entire text run:
     // find script runs, and then call InitScriptRun for each
+    template<typename T>
     void InitTextRun(gfxContext *aContext,
                      gfxTextRun *aTextRun,
-                     const PRUnichar *aString,
+                     const T *aString,
                      PRUint32 aLength);
 
     // InitTextRun helper to handle a single script run, by finding font ranges
     // and calling each font's InitTextRun() as appropriate
+    template<typename T>
     void InitScriptRun(gfxContext *aContext,
                        gfxTextRun *aTextRun,
-                       const PRUnichar *aString,
-                       PRUint32 aTotalLength,
+                       const T *aString,
                        PRUint32 aScriptRunStart,
                        PRUint32 aScriptRunEnd,
                        PRInt32 aRunScript);
 
     /* If aResolveGeneric is true, then CSS/Gecko generic family names are
      * replaced with preferred fonts.
      *
      * If aResolveFontName is true then fc() is called only for existing fonts
--- a/gfx/thebes/gfxGDIFont.cpp
+++ b/gfx/thebes/gfxGDIFont.cpp
@@ -114,48 +114,43 @@ gfxGDIFont::CreatePlatformShaper()
 gfxFont*
 gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
 {
     return new gfxGDIFont(static_cast<GDIFontEntry*>(mFontEntry.get()),
                           &mStyle, mNeedsBold, anAAOption);
 }
 
 static bool
-UseUniscribe(gfxTextRun *aTextRun,
-             const PRUnichar *aString,
-             PRUint32 aRunStart,
-             PRUint32 aRunLength)
+UseUniscribe(gfxShapedWord *aShapedWord,
+             const PRUnichar *aString)
 {
-    PRUint32 flags = aTextRun->GetFlags();
+    PRUint32 flags = aShapedWord->Flags();
     bool useGDI;
 
     bool isXP = (gfxWindowsPlatform::WindowsOSVersion() 
                        < gfxWindowsPlatform::kWindowsVista);
 
     // bug 561304 - Uniscribe bug produces bad positioning at certain
     // font sizes on XP, so default to GDI on XP using logic of 3.6
 
     useGDI = isXP &&
              (flags &
                (gfxTextRunFactory::TEXT_OPTIMIZE_SPEED | 
                 gfxTextRunFactory::TEXT_IS_RTL)
              ) == gfxTextRunFactory::TEXT_OPTIMIZE_SPEED;
 
     return !useGDI ||
-        ScriptIsComplex(aString + aRunStart, aRunLength, SIC_COMPLEX) == S_OK;
+        ScriptIsComplex(aString, aShapedWord->Length(), SIC_COMPLEX) == S_OK;
 }
 
 bool
-gfxGDIFont::InitTextRun(gfxContext *aContext,
-                        gfxTextRun *aTextRun,
-                        const PRUnichar *aString,
-                        PRUint32 aRunStart,
-                        PRUint32 aRunLength,
-                        PRInt32 aRunScript,
-                        bool aPreferPlatformShaping)
+gfxGDIFont::ShapeWord(gfxContext *aContext,
+                      gfxShapedWord *aShapedWord,
+                      const PRUnichar *aString,
+                      bool aPreferPlatformShaping)
 {
     if (!mMetrics) {
         Initialize();
     }
     if (!mIsValid) {
         NS_WARNING("invalid font! expect incorrect text rendering");
         return false;
     }
@@ -163,81 +158,66 @@ gfxGDIFont::InitTextRun(gfxContext *aCon
     bool ok = false;
 
     // ensure the cairo font is set up, so there's no risk it'll fall back to
     // creating a "toy" font internally (see bug 544617)
     SetupCairoFont(aContext);
 
 #ifdef MOZ_GRAPHITE
     if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
-        ok = mGraphiteShaper->InitTextRun(aContext, aTextRun, aString,
-                                          aRunStart, aRunLength, 
-                                          aRunScript);
+        ok = mGraphiteShaper->ShapeWord(aContext, aShapedWord, aString);
     }
 #endif
 
     if (!ok && mHarfBuzzShaper) {
-        if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aRunScript)) {
-            ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength, 
-                                              aRunScript);
+        if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aShapedWord->Script())) {
+            ok = mHarfBuzzShaper->ShapeWord(aContext, aShapedWord, aString);
         }
     }
 
     if (!ok) {
         GDIFontEntry *fe = static_cast<GDIFontEntry*>(GetFontEntry());
         bool preferUniscribe =
             (!fe->IsTrueType() || fe->IsSymbolFont()) && !fe->mForceGDI;
 
-        if (preferUniscribe ||
-            UseUniscribe(aTextRun, aString, aRunStart, aRunLength))
-        {
+        if (preferUniscribe || UseUniscribe(aShapedWord, aString)) {
             // first try Uniscribe
             if (!mUniscribeShaper) {
                 mUniscribeShaper = new gfxUniscribeShaper(this);
             }
 
-            ok = mUniscribeShaper->InitTextRun(aContext, aTextRun, aString,
-                                               aRunStart, aRunLength, 
-                                               aRunScript);
+            ok = mUniscribeShaper->ShapeWord(aContext, aShapedWord, aString);
             if (ok) {
                 return true;
             }
 
             // fallback to GDI shaping
             if (!mPlatformShaper) {
                 CreatePlatformShaper();
             }
 
-            ok = mPlatformShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength, 
-                                              aRunScript);
+            ok = mPlatformShaper->ShapeWord(aContext, aShapedWord, aString);
         } else {
             // first use GDI
             if (!mPlatformShaper) {
                 CreatePlatformShaper();
             }
 
-            ok = mPlatformShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength, 
-                                              aRunScript);
-
+            ok = mPlatformShaper->ShapeWord(aContext, aShapedWord, aString);
             if (ok) {
                 return true;
             }
 
             // try Uniscribe if GDI failed
             if (!mUniscribeShaper) {
                 mUniscribeShaper = new gfxUniscribeShaper(this);
             }
 
             // use Uniscribe shaping
-            ok = mUniscribeShaper->InitTextRun(aContext, aTextRun, aString,
-                                               aRunStart, aRunLength, 
-                                               aRunScript);
+            ok = mUniscribeShaper->ShapeWord(aContext, aShapedWord, aString);
         }
 
 #if DEBUG
         if (!ok) {
             NS_ConvertUTF16toUTF8 name(GetName());
             char msg[256];
 
             sprintf(msg, 
--- a/gfx/thebes/gfxGDIFont.h
+++ b/gfx/thebes/gfxGDIFont.h
@@ -87,23 +87,20 @@ public:
 
     // get hinted glyph width in pixels as 16.16 fixed-point value
     virtual PRInt32 GetGlyphWidth(gfxContext *aCtx, PRUint16 aGID);
 
 protected:
     virtual void CreatePlatformShaper();
 
     /* override to check for uniscribe failure and fall back to GDI */
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript,
-                               bool aPreferPlatformShaping = false);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString,
+                           bool aPreferPlatformShaping = false);
 
     void Initialize(); // creates metrics and Cairo fonts
 
     void FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize);
 
     // mPlatformShaper is used for the GDI shaper, mUniscribeShaper
     // for the Uniscribe version if needed
     nsAutoPtr<gfxFontShaper>   mUniscribeShaper;
--- a/gfx/thebes/gfxGDIShaper.cpp
+++ b/gfx/thebes/gfxGDIShaper.cpp
@@ -45,86 +45,84 @@
 
 /**********************************************************************
  *
  * class gfxGDIShaper
  *
  **********************************************************************/
 
 bool
-gfxGDIShaper::InitTextRun(gfxContext *aContext,
-                          gfxTextRun *aTextRun,
-                          const PRUnichar *aString,
-                          PRUint32 aRunStart,
-                          PRUint32 aRunLength,
-                          PRInt32 aRunScript)
+gfxGDIShaper::ShapeWord(gfxContext *aContext,
+                        gfxShapedWord *aShapedWord,
+                        const PRUnichar *aString)
 {
     DCFromContext dc(aContext);
     AutoSelectFont selectFont(dc, static_cast<gfxGDIFont*>(mFont)->GetHFONT());
 
+    PRUint32 length = aShapedWord->Length();
     nsAutoTArray<WORD,500> glyphArray;
-    if (!glyphArray.SetLength(aRunLength)) {
+    if (!glyphArray.SetLength(length)) {
         return false;
     }
     WORD *glyphs = glyphArray.Elements();
 
-    DWORD ret = ::GetGlyphIndicesW(dc, aString + aRunStart, aRunLength,
+    DWORD ret = ::GetGlyphIndicesW(dc, aString, length,
                                    glyphs, GGI_MARK_NONEXISTING_GLYPHS);
     if (ret == GDI_ERROR) {
         return false;
     }
 
-    for (int k = 0; k < aRunLength; k++) {
+    for (int k = 0; k < length; k++) {
         if (glyphs[k] == 0xFFFF)
             return false;
     }
  
     SIZE size;
     nsAutoTArray<int,500> partialWidthArray;
-    if (!partialWidthArray.SetLength(aRunLength)) {
+    if (!partialWidthArray.SetLength(length)) {
         return false;
     }
 
     BOOL success = ::GetTextExtentExPointI(dc,
                                            glyphs,
-                                           aRunLength,
+                                           length,
                                            INT_MAX,
                                            NULL,
                                            partialWidthArray.Elements(),
                                            &size);
     if (!success) {
         return false;
     }
 
     gfxTextRun::CompressedGlyph g;
     PRUint32 i;
     PRInt32 lastWidth = 0;
-    PRUint32 appUnitsPerDevPixel = aTextRun->GetAppUnitsPerDevUnit();
-    for (i = 0; i < aRunLength; ++i) {
-        PRUint32 offset = aRunStart + i;
+    PRUint32 appUnitsPerDevPixel = aShapedWord->AppUnitsPerDevUnit();
+    for (i = 0; i < length; ++i) {
+        PRUint32 offset = i;
         PRInt32 advancePixels = partialWidthArray[i] - lastWidth;
         lastWidth = partialWidthArray[i];
-        PRInt32 advanceAppUnits = advancePixels*appUnitsPerDevPixel;
+        PRInt32 advanceAppUnits = advancePixels * appUnitsPerDevPixel;
         WCHAR glyph = glyphs[i];
-        NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aTextRun->GetChar(offset)),
+        NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aShapedWord->GetCharAt(offset)),
                      "Invalid character detected!");
-        bool atClusterStart = aTextRun->IsClusterStart(offset);
+        bool atClusterStart = aShapedWord->IsClusterStart(offset);
         if (advanceAppUnits >= 0 &&
-            gfxTextRun::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) &&
-            gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(glyph) &&
             atClusterStart)
         {
-            aTextRun->SetSimpleGlyph(offset,
-                                     g.SetSimpleGlyph(advanceAppUnits, glyph));
+            aShapedWord->SetSimpleGlyph(offset,
+                                        g.SetSimpleGlyph(advanceAppUnits, glyph));
         } else {
-            gfxTextRun::DetailedGlyph details;
+            gfxShapedWord::DetailedGlyph details;
             details.mGlyphID = glyph;
             details.mAdvance = advanceAppUnits;
             details.mXOffset = 0;
             details.mYOffset = 0;
-            aTextRun->SetGlyphs(offset,
-                                g.SetComplex(atClusterStart, true, 1),
-                                &details);
+            aShapedWord->SetGlyphs(offset,
+                                   g.SetComplex(atClusterStart, true, 1),
+                                   &details);
         }
     }
 
     return true;
 }
--- a/gfx/thebes/gfxGDIShaper.h
+++ b/gfx/thebes/gfxGDIShaper.h
@@ -49,17 +49,14 @@ public:
         MOZ_COUNT_CTOR(gfxGDIShaper);
     }
 
     virtual ~gfxGDIShaper()
     {
         MOZ_COUNT_DTOR(gfxGDIShaper);
     }
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString);
 };
 
 #endif /* GFX_GDISHAPER_H */
--- a/gfx/thebes/gfxGraphiteShaper.cpp
+++ b/gfx/thebes/gfxGraphiteShaper.cpp
@@ -156,22 +156,19 @@ MakeGraphiteLangTag(PRUint32 aTag)
     while ((grLangTag & mask) == ' ') {
         grLangTag &= ~mask;
         mask <<= 8;
     }
     return grLangTag;
 }
 
 bool
-gfxGraphiteShaper::InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript)
+gfxGraphiteShaper::ShapeWord(gfxContext      *aContext,
+                             gfxShapedWord   *aShapedWord,
+                             const PRUnichar *aText)
 {
     // some font back-ends require this in order to get proper hinted metrics
     mFont->SetupCairoFont(aContext);
 
     mCallbackData.mContext = aContext;
 
     if (!mGrFont) {
         mGrFace = gr_make_face(&mCallbackData, GrGetTable, gr_face_default);
@@ -185,73 +182,61 @@ gfxGraphiteShaper::InitTextRun(gfxContex
             gr_make_font(mFont->GetAdjustedSize(), mGrFace);
         if (!mGrFont) {
             gr_face_destroy(mGrFace);
             mGrFace = nsnull;
             return false;
         }
     }
 
-    const gfxFontStyle *style = aTextRun->GetFontGroup()->GetStyle();
+    gfxFontEntry *entry = mFont->GetFontEntry();
+    const gfxFontStyle *style = mFont->GetStyle();
     PRUint32 grLang = 0;
     if (style->languageOverride) {
         grLang = MakeGraphiteLangTag(style->languageOverride);
-    } else if (mFont->GetFontEntry()->mLanguageOverride) {
-        grLang = MakeGraphiteLangTag(mFont->GetFontEntry()->mLanguageOverride);
+    } else if (entry->mLanguageOverride) {
+        grLang = MakeGraphiteLangTag(entry->mLanguageOverride);
     } else {
         nsCAutoString langString;
         style->language->ToUTF8String(langString);
         grLang = GetGraphiteTagForLang(langString);
     }
     gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang);
 
-    bool disableLigatures =
-        (aTextRun->GetFlags() &
-         gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0;
-    if (disableLigatures) {
+    if (aShapedWord->DisableLigatures()) {
         const gr_feature_ref* fref =
             gr_face_find_fref(mGrFace, TRUETYPE_TAG('l','i','g','a'));
         if (fref) {
             gr_fref_set_feature_value(fref, 0, grFeatures);
         }
     }
 
     const nsTArray<gfxFontFeature> *features = &style->featureSettings;
     if (features->IsEmpty()) {
-        features = &mFont->GetFontEntry()->mFeatureSettings;
+        features = &entry->mFeatureSettings;
     }
     for (PRUint32 i = 0; i < features->Length(); ++i) {
         const gr_feature_ref* fref =
             gr_face_find_fref(mGrFace, (*features)[i].mTag);
         if (fref) {
             gr_fref_set_feature_value(fref, (*features)[i].mValue, grFeatures);
         }
     }
 
-    const PRUnichar *textStart = aString + aRunStart;
-    const PRUnichar *textEnd = textStart + aRunLength;
-    const void *pError;
-    size_t nChars = gr_count_unicode_characters(gr_utf16,
-                                                textStart, textEnd,
-                                                &pError);
-    if (pError != nsnull) {
-        return false;
-    }
-
     gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures,
-                                  gr_utf16, textStart, nChars,
-                                  aTextRun->IsRightToLeft());
+                                  gr_utf16, aText, aShapedWord->Length(),
+                                  aShapedWord->IsRightToLeft());
     if (features) {
         gr_featureval_destroy(grFeatures);
     }
     if (!seg) {
         return false;
     }
 
-    nsresult rv = SetGlyphsFromSegment(aTextRun, aRunStart, aRunLength, seg);
+    nsresult rv = SetGlyphsFromSegment(aShapedWord, seg);
 
     gr_seg_destroy(seg);
 
     return NS_SUCCEEDED(rv);
 }
 
 #define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays
                             // for short (typical) runs up to this length
@@ -260,33 +245,31 @@ struct Cluster {
     PRUint32 baseChar;
     PRUint32 baseGlyph;
     PRUint32 nChars;
     PRUint32 nGlyphs;
     Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { }
 };
 
 nsresult
-gfxGraphiteShaper::SetGlyphsFromSegment(gfxTextRun *aTextRun,
-                                        PRUint32 aRunStart,
-                                        PRUint32 aRunLength,
+gfxGraphiteShaper::SetGlyphsFromSegment(gfxShapedWord *aShapedWord,
                                         gr_segment *aSegment)
 {
-    PRInt32 dev2appUnits = aTextRun->GetAppUnitsPerDevUnit();
-    bool rtl = aTextRun->IsRightToLeft();
+    PRInt32 dev2appUnits = aShapedWord->AppUnitsPerDevUnit();
+    bool rtl = aShapedWord->IsRightToLeft();
 
     PRUint32 glyphCount = gr_seg_n_slots(aSegment);
 
     // identify clusters; graphite may have reordered/expanded/ligated glyphs.
     nsAutoTArray<Cluster,SMALL_GLYPH_RUN> clusters;
     nsAutoTArray<PRUint16,SMALL_GLYPH_RUN> gids;
     nsAutoTArray<float,SMALL_GLYPH_RUN> xLocs;
     nsAutoTArray<float,SMALL_GLYPH_RUN> yLocs;
 
-    if (!clusters.SetLength(aRunLength) ||
+    if (!clusters.SetLength(aShapedWord->Length()) ||
         !gids.SetLength(glyphCount) ||
         !xLocs.SetLength(glyphCount) ||
         !yLocs.SetLength(glyphCount))
     {
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
     // walk through the glyph slots and check which original character
@@ -312,27 +295,27 @@ gfxGraphiteShaper::SetGlyphsFromSegment(
             --cIndex;
         }
 
         // if there's a gap between the current cluster's base character and
         // this glyph's, extend the cluster to include the intervening chars
         if (gr_slot_can_insert_before(slot) && clusters[cIndex].nChars &&
             before >= clusters[cIndex].baseChar + clusters[cIndex].nChars)
         {
-            NS_ASSERTION(cIndex < aRunLength - 1, "cIndex at end of run");
+            NS_ASSERTION(cIndex < aShapedWord->Length() - 1, "cIndex at end of word");
             Cluster& c = clusters[cIndex + 1];
             c.baseChar = clusters[cIndex].baseChar + clusters[cIndex].nChars;
             c.nChars = before - c.baseChar;
             c.baseGlyph = gIndex;
             c.nGlyphs = 0;
             ++cIndex;
         }
 
         // increment cluster's glyph count to include current slot
-        NS_ASSERTION(cIndex < aRunLength, "cIndex beyond valid run length");
+        NS_ASSERTION(cIndex < aShapedWord->Length(), "cIndex beyond word length");
         ++clusters[cIndex].nGlyphs;
 
         // extend cluster if necessary to reach the glyph's "after" index
         if (clusters[cIndex].baseChar + clusters[cIndex].nChars < after + 1) {
             clusters[cIndex].nChars = after + 1 - clusters[cIndex].baseChar;
         }
     }
 
@@ -353,65 +336,64 @@ gfxGraphiteShaper::SetGlyphsFromSegment(
             } else {
                 adv = xLocs[clusters[i+1].baseGlyph] - xLocs[c.baseGlyph];
             }
         }
 
         // Check for default-ignorable char that didn't get filtered, combined,
         // etc by the shaping process, and skip it.
         PRUint32 offs = gr_cinfo_base(gr_seg_cinfo(aSegment, c.baseChar));
-        NS_ASSERTION(offs >= c.baseChar && offs < aRunLength,
+        NS_ASSERTION(offs >= c.baseChar && offs < aShapedWord->Length(),
                      "unexpected offset");
         if (c.nGlyphs == 1 && c.nChars == 1 &&
-            aTextRun->FilterIfIgnorable(aRunStart + offs))
+            aShapedWord->FilterIfIgnorable(offs))
         {
             continue;
         }
 
         PRUint32 appAdvance = adv * dev2appUnits;
         if (c.nGlyphs == 1 &&
-            gfxTextRun::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) &&
-            gfxTextRun::CompressedGlyph::IsSimpleAdvance(appAdvance) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleAdvance(appAdvance) &&
             yLocs[c.baseGlyph] == 0)
         {
-            gfxTextRun::CompressedGlyph g;
-            aTextRun->SetSimpleGlyph(aRunStart + offs,
-                                     g.SetSimpleGlyph(appAdvance,
-                                                      gids[c.baseGlyph]));
+            gfxShapedWord::CompressedGlyph g;
+            aShapedWord->SetSimpleGlyph(offs,
+                                        g.SetSimpleGlyph(appAdvance,
+                                                         gids[c.baseGlyph]));
         } else {
             // not a one-to-one mapping with simple metrics: use DetailedGlyph
-            nsAutoTArray<gfxTextRun::DetailedGlyph,8> details;
+            nsAutoTArray<gfxShapedWord::DetailedGlyph,8> details;
             float clusterLoc;
             for (PRUint32 j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) {
-                gfxTextRun::DetailedGlyph* d = details.AppendElement();
+                gfxShapedWord::DetailedGlyph* d = details.AppendElement();
                 d->mGlyphID = gids[j];
                 d->mYOffset = -yLocs[j] * dev2appUnits;
                 if (j == c.baseGlyph) {
                     d->mXOffset = 0;
                     d->mAdvance = appAdvance;
                     clusterLoc = xLocs[j];
                 } else {
                     d->mXOffset = (xLocs[j] - clusterLoc - adv) * dev2appUnits;
                     d->mAdvance = 0;
                 }
             }
-            gfxTextRun::CompressedGlyph g;
-            g.SetComplex(aTextRun->IsClusterStart(aRunStart + offs),
+            gfxShapedWord::CompressedGlyph g;
+            g.SetComplex(aShapedWord->IsClusterStart(offs),
                          true, details.Length());
-            aTextRun->SetGlyphs(aRunStart + offs, g, details.Elements());
+            aShapedWord->SetGlyphs(offs, g, details.Elements());
         }
 
         for (PRUint32 j = c.baseChar + 1; j < c.baseChar + c.nChars; ++j) {
             offs = gr_cinfo_base(gr_seg_cinfo(aSegment, j));
-            NS_ASSERTION(offs >= j && offs < aRunLength,
+            NS_ASSERTION(offs >= j && offs < aShapedWord->Length(),
                          "unexpected offset");
-            gfxTextRun::CompressedGlyph g;
-            g.SetComplex(aTextRun->IsClusterStart(aRunStart + offs),
-                         false, 0);
-            aTextRun->SetGlyphs(aRunStart + offs, g, nsnull);
+            gfxShapedWord::CompressedGlyph g;
+            g.SetComplex(aShapedWord->IsClusterStart(offs), false, 0);
+            aShapedWord->SetGlyphs(offs, g, nsnull);
         }
     }
 
     return NS_OK;
 }
 
 // for language tag validation - include list of tags from the IANA registry
 #include "gfxLanguageTagList.cpp"
--- a/gfx/thebes/gfxGraphiteShaper.h
+++ b/gfx/thebes/gfxGraphiteShaper.h
@@ -47,22 +47,19 @@ class gr_face;
 class gr_font;
 class gr_segment;
 
 class gfxGraphiteShaper : public gfxFontShaper {
 public:
     gfxGraphiteShaper(gfxFont *aFont);
     virtual ~gfxGraphiteShaper();
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aText);
 
     const void* GetTable(PRUint32 aTag, size_t *aLength);
 
     static void Shutdown();
 
     struct CallbackData {
         gfxFont           *mFont;
         gfxGraphiteShaper *mShaper;
@@ -71,19 +68,17 @@ public:
 
     struct TableRec {
         hb_blob_t  *mBlob;
         const void *mData;
         PRUint32    mLength;
     };
 
 protected:
-    nsresult SetGlyphsFromSegment(gfxTextRun *aTextRun,
-                                  PRUint32 aRunStart,
-                                  PRUint32 aRunLength,
+    nsresult SetGlyphsFromSegment(gfxShapedWord *aShapedWord,
                                   gr_segment *aSegment);
 
     gr_face *mGrFace;
     gr_font *mGrFont;
 
     CallbackData mCallbackData;
 
     nsDataHashtable<nsUint32HashKey,TableRec> mTables;
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -708,22 +708,19 @@ HBGetEastAsianWidth(hb_codepoint_t aCh)
 /*
  * gfxFontShaper override to initialize the text run using HarfBuzz
  */
 
 static hb_font_funcs_t * sHBFontFuncs = nsnull;
 static hb_unicode_funcs_t * sHBUnicodeFuncs = nsnull;
 
 bool
-gfxHarfBuzzShaper::InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript)
+gfxHarfBuzzShaper::ShapeWord(gfxContext      *aContext,
+                             gfxShapedWord   *aShapedWord,
+                             const PRUnichar *aText)
 {
     // some font back-ends require this in order to get proper hinted metrics
     mFont->SetupCairoFont(aContext);
 
     if (!mHBFace) {
 
         mUseFontGlyphWidths = mFont->ProvidesGlyphWidths();
 
@@ -813,92 +810,87 @@ gfxHarfBuzzShaper::InitTextRun(gfxContex
 
     FontCallbackData fcd(this, aContext);
     hb_font_t *font = hb_font_create();
     hb_font_set_funcs(font, sHBFontFuncs, nsnull, &fcd);
     hb_font_set_ppem(font, mFont->GetAdjustedSize(), mFont->GetAdjustedSize());
     PRUint32 scale = FloatToFixed(mFont->GetAdjustedSize()); // 16.16 fixed-point
     hb_font_set_scale(font, scale, scale);
 
-    // aRunStart and aRunLength define the section of the textRun and of
-    // aString that is to be drawn with this particular font
-
-    bool disableLigatures =
-        (aTextRun->GetFlags() &
-         gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0;
-
     nsAutoTArray<hb_feature_t,20> features;
 
     // Ligature features are enabled by default in the generic shaper,
     // so we explicitly turn them off if necessary (for letter-spacing)
-    if (disableLigatures) {
+    if (aShapedWord->DisableLigatures()) {
         hb_feature_t ligaOff = { HB_TAG('l','i','g','a'), 0, 0, UINT_MAX };
         hb_feature_t cligOff = { HB_TAG('c','l','i','g'), 0, 0, UINT_MAX };
         features.AppendElement(ligaOff);
         features.AppendElement(cligOff);
     }
 
     // css features need to be merged with the existing ones, if any
-    const gfxFontStyle *style = aTextRun->GetFontGroup()->GetStyle();
+    gfxFontEntry *entry = mFont->GetFontEntry();
+    const gfxFontStyle *style = mFont->GetStyle();
     const nsTArray<gfxFontFeature> *cssFeatures = &style->featureSettings;
     if (cssFeatures->IsEmpty()) {
-        cssFeatures = &mFont->GetFontEntry()->mFeatureSettings;
+        cssFeatures = &entry->mFeatureSettings;
     }
     for (PRUint32 i = 0; i < cssFeatures->Length(); ++i) {
         PRUint32 j;
         for (j = 0; j < features.Length(); ++j) {
             if (cssFeatures->ElementAt(i).mTag == features[j].tag) {
                 features[j].value = cssFeatures->ElementAt(i).mValue;
                 break;
             }
         }
         if (j == features.Length()) {
             const gfxFontFeature& f = cssFeatures->ElementAt(i);
             hb_feature_t hbf = { f.mTag, f.mValue, 0, UINT_MAX };
             features.AppendElement(hbf);
         }
     }
 
-    hb_buffer_t *buffer = hb_buffer_create(aRunLength);
+    bool isRightToLeft = aShapedWord->IsRightToLeft();
+    hb_buffer_t *buffer = hb_buffer_create(aShapedWord->Length());
     hb_buffer_set_unicode_funcs(buffer, sHBUnicodeFuncs);
-    hb_buffer_set_direction(buffer,
-                            aTextRun->IsRightToLeft() ?
-                                HB_DIRECTION_RTL : HB_DIRECTION_LTR);
+    hb_buffer_set_direction(buffer, isRightToLeft ? HB_DIRECTION_RTL :
+                                                    HB_DIRECTION_LTR);
     // For unresolved "common" or "inherited" runs, default to Latin for now.
     // (Should we somehow use the language or locale to try and infer
     // a better default?)
     hb_buffer_set_script(buffer,
-                         aRunScript <= HB_SCRIPT_INHERITED ? HB_SCRIPT_LATIN
-                         : hb_script_t(aRunScript));
+                         aShapedWord->Script() <= HB_SCRIPT_INHERITED ?
+                             HB_SCRIPT_LATIN :
+                             hb_script_t(aShapedWord->Script()));
 
     hb_language_t language;
     if (style->languageOverride) {
         language = hb_ot_tag_to_language(style->languageOverride);
-    } else if (mFont->GetFontEntry()->mLanguageOverride) {
-        language =
-            hb_ot_tag_to_language(mFont->GetFontEntry()->mLanguageOverride);
+    } else if (entry->mLanguageOverride) {
+        language = hb_ot_tag_to_language(entry->mLanguageOverride);
     } else {
         nsCString langString;
         style->language->ToUTF8String(langString);
         language = hb_language_from_string(langString.get());
     }
     hb_buffer_set_language(buffer, language);
 
-    hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16_t*>(aString + aRunStart),
-                        aRunLength, 0, aRunLength);
+    PRUint32 length = aShapedWord->Length();
+    hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16_t*>(aText),
+                        length, 0, length);
 
     hb_shape(font, mHBFace, buffer, features.Elements(), features.Length());
 
-    if (aTextRun->IsRightToLeft()) {
+    if (isRightToLeft) {
         hb_buffer_reverse(buffer);
     }
 
-    nsresult rv =
-        SetGlyphsFromRun(aContext, aTextRun, buffer, aRunStart, aRunLength);
-    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to store glyphs into textrun");
+    nsresult rv = SetGlyphsFromRun(aContext, aShapedWord, buffer);
+
+    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to store glyphs into gfxShapedWord");
     hb_buffer_destroy(buffer);
     hb_font_destroy(font);
 
     return NS_SUCCEEDED(rv);
 }
 
 /**
  * Work out whether cairo will snap inter-glyph spacing to pixels.
@@ -975,69 +967,71 @@ GetRoundOffsetsToPixels(gfxContext *aCon
 }
 
 #define SMALL_GLYPH_RUN 128 // some testing indicates that 90%+ of text runs
                             // will fit without requiring separate allocation
                             // for charToGlyphArray
 
 nsresult
 gfxHarfBuzzShaper::SetGlyphsFromRun(gfxContext *aContext,
-                                    gfxTextRun *aTextRun,
-                                    hb_buffer_t *aBuffer,
-                                    PRUint32 aTextRunOffset,
-                                    PRUint32 aRunLength)
+                                    gfxShapedWord *aShapedWord,
+                                    hb_buffer_t *aBuffer)
 {
     PRInt32 numGlyphs = hb_buffer_get_length(aBuffer);
-    if (numGlyphs == 0)
+    if (numGlyphs == 0) {
         return NS_OK;
+    }
 
     const hb_glyph_info_t *ginfo = hb_buffer_get_glyph_infos(aBuffer);
 
     nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs;
 
+    PRUint32 wordLength = aShapedWord->Length();
     static const PRInt32 NO_GLYPH = -1;
     nsAutoTArray<PRInt32,SMALL_GLYPH_RUN> charToGlyphArray;
-    if (!charToGlyphArray.SetLength(aRunLength))
+    if (!charToGlyphArray.SetLength(wordLength)) {
         return NS_ERROR_OUT_OF_MEMORY;
+    }
 
     PRInt32 *charToGlyph = charToGlyphArray.Elements();
-    for (PRUint32 offset = 0; offset < aRunLength; ++offset) {
+    for (PRUint32 offset = 0; offset < wordLength; ++offset) {
         charToGlyph[offset] = NO_GLYPH;
     }
 
     for (PRInt32 i = 0; i < numGlyphs; ++i) {
         PRUint32 loc = ginfo[i].cluster;
-        if (loc < aRunLength) {
+        if (loc < wordLength) {
             charToGlyph[loc] = i;
         }
     }
 
     PRInt32 glyphStart = 0; // looking for a clump that starts at this glyph
     PRInt32 charStart = 0; // and this char index within the range of the run
 
     bool roundX;
     bool roundY;
     GetRoundOffsetsToPixels(aContext, &roundX, &roundY);
-    // This is signed to avoid promotion to unsigned.
-    PRInt32 dev2appUnits = aTextRun->GetAppUnitsPerDevUnit();
+
+    PRInt32 appUnitsPerDevUnit = aShapedWord->AppUnitsPerDevUnit();
+
     // factor to convert 16.16 fixed-point pixels to app units
     // (only used if not rounding)
-    double hb2appUnits = FixedToFloat(aTextRun->GetAppUnitsPerDevUnit());
+    double hb2appUnits = FixedToFloat(aShapedWord->AppUnitsPerDevUnit());
 
     // keep track of y-position to set glyph offsets if needed
     nscoord yPos = 0;
 
     const hb_glyph_position_t *posInfo = hb_buffer_get_glyph_positions(aBuffer);
 
     while (glyphStart < numGlyphs) {
 
         bool inOrder = true;
         PRInt32 charEnd = ginfo[glyphStart].cluster;
         PRInt32 glyphEnd = glyphStart;
-        PRInt32 charLimit = aRunLength;
+        PRInt32 charLimit = wordLength;
         while (charEnd < charLimit) {
             // This is normally executed once for each iteration of the outer loop,
             // but in unusual cases where the character/glyph association is complex,
             // the initial character range might correspond to a non-contiguous
             // glyph range with "holes" in it. If so, we will repeat this loop to
             // extend the character range until we have a contiguous glyph sequence.
             charEnd += 1;
             while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) {
@@ -1089,59 +1083,59 @@ gfxHarfBuzzShaper::SetGlyphsFromRun(gfxC
         NS_ASSERTION(charStart != charEnd,
                      "character/glyph clump contains no characters!");
 
         // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd;
         // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature),
         // and endCharIndex to the limit (position beyond the last char),
         // adjusting for the offset of the stringRange relative to the textRun.
         PRInt32 baseCharIndex, endCharIndex;
-        while (charEnd < PRInt32(aRunLength) && charToGlyph[charEnd] == NO_GLYPH)
+        while (charEnd < PRInt32(wordLength) && charToGlyph[charEnd] == NO_GLYPH)
             charEnd++;
         baseCharIndex = charStart;
         endCharIndex = charEnd;
 
         // Then we check if the clump falls outside our actual string range;
         // if so, just go to the next.
-        if (baseCharIndex >= PRInt32(aRunLength)) {
+        if (baseCharIndex >= PRInt32(wordLength)) {
             glyphStart = glyphEnd;
             charStart = charEnd;
             continue;
         }
         // Ensure we won't try to go beyond the valid length of the textRun's text
-        endCharIndex = NS_MIN<PRInt32>(endCharIndex, aRunLength);
+        endCharIndex = NS_MIN<PRInt32>(endCharIndex, wordLength);
 
         // Now we're ready to set the glyph info in the textRun
         PRInt32 glyphsInClump = glyphEnd - glyphStart;
 
         // Check for default-ignorable char that didn't get filtered, combined,
         // etc by the shaping process, and remove from the run.
         // (This may be done within harfbuzz eventually.)
         if (glyphsInClump == 1 && baseCharIndex + 1 == endCharIndex &&
-            aTextRun->FilterIfIgnorable(aTextRunOffset + baseCharIndex)) {
+            aShapedWord->FilterIfIgnorable(baseCharIndex)) {
             glyphStart = glyphEnd;
             charStart = charEnd;
             continue;
         }
 
         // Check if it's a simple one-to-one mapping
         hb_position_t x_advance = posInfo[glyphStart].x_advance;
         nscoord advance =
-            roundX ? dev2appUnits * FixedToIntRound(x_advance)
+            roundX ? appUnitsPerDevUnit * FixedToIntRound(x_advance)
             : floor(hb2appUnits * x_advance + 0.5);
 
         if (glyphsInClump == 1 &&
             gfxTextRun::CompressedGlyph::IsSimpleGlyphID(ginfo[glyphStart].codepoint) &&
             gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
-            aTextRun->IsClusterStart(aTextRunOffset + baseCharIndex) &&
+            aShapedWord->IsClusterStart(baseCharIndex) &&
             posInfo[glyphStart].x_offset == 0 &&
             posInfo[glyphStart].y_offset == 0 && yPos == 0)
         {
             gfxTextRun::CompressedGlyph g;
-            aTextRun->SetSimpleGlyph(aTextRunOffset + baseCharIndex,
+            aShapedWord->SetSimpleGlyph(baseCharIndex,
                                      g.SetSimpleGlyph(advance,
                                          ginfo[glyphStart].codepoint));
         } else {
             // collect all glyphs in a list to be assigned to the first char;
             // there must be at least one in the clump, and we already measured
             // its advance, hence the placement of the loop-exit test and the
             // measurement of the next glyph
             while (1) {
@@ -1150,57 +1144,57 @@ gfxHarfBuzzShaper::SetGlyphsFromRun(gfxC
                 details->mGlyphID = ginfo[glyphStart].codepoint;
 
                 // Rounding offsets independently of advances on the assumption
                 // that clusters use offsets and rounding of offsets should
                 // not accumulate, and that advances are typically between
                 // clusters.
                 hb_position_t x_offset = posInfo[glyphStart].x_offset;
                 details->mXOffset =
-                    roundX ? dev2appUnits * FixedToIntRound(x_offset)
+                    roundX ? appUnitsPerDevUnit * FixedToIntRound(x_offset)
                     : floor(hb2appUnits * x_offset + 0.5);
                 hb_position_t y_offset = posInfo[glyphStart].y_offset;
                 details->mYOffset = yPos -
-                    (roundY ? dev2appUnits * FixedToIntRound(y_offset)
+                    (roundY ? appUnitsPerDevUnit * FixedToIntRound(y_offset)
                      : floor(hb2appUnits * y_offset + 0.5));
 
                 details->mAdvance = advance;
                 hb_position_t y_advance = posInfo[glyphStart].y_advance;
                 if (y_advance != 0) {
                     yPos -=
-                        roundY ? dev2appUnits * FixedToIntRound(y_advance)
+                        roundY ? appUnitsPerDevUnit * FixedToIntRound(y_advance)
                         : floor(hb2appUnits * y_advance + 0.5);
                 }
                 if (++glyphStart >= glyphEnd) {
                     break;
                 }
                 x_advance = posInfo[glyphStart].x_advance;
                 advance =
-                    roundX ? dev2appUnits * FixedToIntRound(x_advance)
+                    roundX ? appUnitsPerDevUnit * FixedToIntRound(x_advance)
                     : floor(hb2appUnits * x_advance + 0.5);
             }
 
             gfxTextRun::CompressedGlyph g;
-            g.SetComplex(aTextRun->IsClusterStart(aTextRunOffset + baseCharIndex),
+            g.SetComplex(aShapedWord->IsClusterStart(baseCharIndex),
                          true, detailedGlyphs.Length());
-            aTextRun->SetGlyphs(aTextRunOffset + baseCharIndex,
+            aShapedWord->SetGlyphs(baseCharIndex,
                                 g, detailedGlyphs.Elements());
 
             detailedGlyphs.Clear();
         }
 
         // the rest of the chars in the group are ligature continuations,
         // no associated glyphs
         while (++baseCharIndex != endCharIndex &&
-               baseCharIndex < PRInt32(aRunLength)) {
+               baseCharIndex < PRInt32(wordLength)) {
             gfxTextRun::CompressedGlyph g;
             g.SetComplex(inOrder &&
-                         aTextRun->IsClusterStart(aTextRunOffset + baseCharIndex),
+                         aShapedWord->IsClusterStart(baseCharIndex),
                          false, 0);
-            aTextRun->SetGlyphs(aTextRunOffset + baseCharIndex, g, nsnull);
+            aShapedWord->SetGlyphs(baseCharIndex, g, nsnull);
         }
 
         glyphStart = glyphEnd;
         charStart = charEnd;
     }
 
     return NS_OK;
 }
--- a/gfx/thebes/gfxHarfBuzzShaper.h
+++ b/gfx/thebes/gfxHarfBuzzShaper.h
@@ -45,22 +45,19 @@
 
 #include "harfbuzz/hb.h"
 
 class gfxHarfBuzzShaper : public gfxFontShaper {
 public:
     gfxHarfBuzzShaper(gfxFont *aFont);
     virtual ~gfxHarfBuzzShaper();
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aText);
 
     // get a given font table in harfbuzz blob form
     hb_blob_t * GetFontTable(hb_tag_t aTag) const;
 
     // map unicode character to glyph ID
     hb_codepoint_t GetGlyph(hb_codepoint_t unicode,
                             hb_codepoint_t variation_selector) const;
 
@@ -69,22 +66,19 @@ public:
                          hb_codepoint_t glyph,
                          hb_position_t *x_advance,
                          hb_position_t *y_advance) const;
 
     hb_position_t GetKerning(PRUint16 aFirstGlyph,
                              PRUint16 aSecondGlyph) const;
 
 protected:
-    // extract glyphs from HarfBuzz buffer and store into the gfxTextRun
     nsresult SetGlyphsFromRun(gfxContext *aContext,
-                              gfxTextRun *aTextRun,
-                              hb_buffer_t *aBuffer,
-                              PRUint32 aTextRunOffset,
-                              PRUint32 aRunLength);
+                              gfxShapedWord *aShapedWord,
+                              hb_buffer_t *aBuffer);
 
     // retrieve glyph positions, applying advance adjustments and attachments
     // returns results in appUnits
     nscoord GetGlyphPositions(gfxContext *aContext,
                               hb_buffer_t *aBuffer,
                               nsTArray<nsPoint>& aPositions,
                               PRUint32 aAppUnitsPerDevUnit);
 
--- a/gfx/thebes/gfxMacFont.cpp
+++ b/gfx/thebes/gfxMacFont.cpp
@@ -151,36 +151,29 @@ gfxMacFont::~gfxMacFont()
         cairo_scaled_font_destroy(mScaledFont);
     }
     if (mFontFace) {
         cairo_font_face_destroy(mFontFace);
     }
 }
 
 bool
-gfxMacFont::InitTextRun(gfxContext *aContext,
-                        gfxTextRun *aTextRun,
-                        const PRUnichar *aString,
-                        PRUint32 aRunStart,
-                        PRUint32 aRunLength,
-                        PRInt32 aRunScript,
-                        bool aPreferPlatformShaping)
+gfxMacFont::ShapeWord(gfxContext *aContext,
+                      gfxShapedWord *aShapedWord,
+                      const PRUnichar *aText,
+                      bool aPreferPlatformShaping)
 {
     if (!mIsValid) {
         NS_WARNING("invalid font! expect incorrect text rendering");
         return false;
     }
 
-    bool ok = gfxFont::InitTextRun(aContext, aTextRun, aString,
-                                     aRunStart, aRunLength, aRunScript,
-        static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout());
-
-    aTextRun->AdjustAdvancesForSyntheticBold(aContext, aRunStart, aRunLength);
-
-    return ok;
+    bool requiresAAT =
+        static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout();
+    return gfxFont::ShapeWord(aContext, aShapedWord, aText, requiresAAT);
 }
 
 void
 gfxMacFont::CreatePlatformShaper()
 {
     mPlatformShaper = new gfxCoreTextShaper(this);
 }
 
--- a/gfx/thebes/gfxMacFont.h
+++ b/gfx/thebes/gfxMacFont.h
@@ -80,23 +80,20 @@ public:
     virtual hb_blob_t *GetFontTable(PRUint32 aTag);
 
     mozilla::RefPtr<mozilla::gfx::ScaledFont> GetScaledFont();
 
 protected:
     virtual void CreatePlatformShaper();
 
     // override to prefer CoreText shaping with fonts that depend on AAT
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript,
-                               bool aPreferPlatformShaping = false);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aText,
+                           bool aPreferPlatformShaping = false);
 
     void InitMetrics();
     void InitMetricsFromPlatform();
     void InitMetricsFromATSMetrics(ATSFontRef aFontRef);
 
     // Get width and glyph ID for a character; uses aConvFactor
     // to convert font units as returned by CG to actual dimensions
     gfxFloat GetCharWidth(CFDataRef aCmap, PRUnichar aUniChar,
--- a/gfx/thebes/gfxPangoFonts.cpp
+++ b/gfx/thebes/gfxPangoFonts.cpp
@@ -114,18 +114,19 @@ struct gfxPangoFcFont;
 int moz_pango_units_from_double(double d) {
     return NS_lround(d * FLOAT_PANGO_SCALE);
 }
 
 static PangoLanguage *GuessPangoLanguage(nsIAtom *aLanguage);
 
 static cairo_scaled_font_t *
 CreateScaledFont(FcPattern *aPattern, cairo_font_face_t *aFace);
-static void SetMissingGlyphs(gfxTextRun *aTextRun, const gchar *aUTF8,
-                             PRUint32 aUTF8Length, PRUint32 *aUTF16Offset);
+static void SetMissingGlyphs(gfxShapedWord *aShapedWord, const gchar *aUTF8,
+                             PRUint32 aUTF8Length, PRUint32 *aUTF16Offset,
+                             gfxFont *aFont);
 
 static PangoFontMap *gPangoFontMap;
 static PangoFontMap *GetPangoFontMap();
 static bool gUseFontMapProperty;
 
 static FT_Library gFTLibrary;
 
 template <class T>
@@ -320,17 +321,17 @@ gfxFcFontEntry::ShouldUseHarfBuzz(PRInt3
         FcPatternGetString(mPatterns[0],
                            FC_CAPABILITY, 0, &capability) == FcResultNoMatch ||
         !FcStrStr(capability, gfxFontconfigUtils::ToFcChar8("ttable:Silf")))
     {
         mSkipGraphiteCheck = true;
         return true;
     }
 
-    // Mimicing gfxHarfBuzzShaper::InitTextRun
+    // Mimicing gfxHarfBuzzShaper::ShapeWord
     hb_script_t script =
         aRunScript <= HB_SCRIPT_INHERITED ? HB_SCRIPT_LATIN
         : static_cast<hb_script_t>(aRunScript);
 
     // Prefer HarfBuzz if the font also has support for OpenType shaping of
     // this script.
     const FcChar8 otCapTemplate[] = "otlayout:XXXX";
     FcChar8 otCap[NS_ARRAY_LENGTH(otCapTemplate)];
@@ -790,42 +791,37 @@ gfxDownloadedFcFontEntry::GetPangoCovera
  * This is a gfxFont implementation using a CAIRO_FONT_TYPE_FT
  * cairo_scaled_font created from an FcPattern.
  */
 
 class gfxFcFont : public gfxFT2FontBase {
 public:
     virtual ~gfxFcFont();
     static already_AddRefed<gfxFcFont>
-    GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern);
+    GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern,
+                  const gfxFontStyle *aFontStyle);
 
     // The PangoFont returned is owned by the gfxFcFont
     PangoFont *GetPangoFont() {
         if (!mPangoFont) {
             MakePangoFont();
         }
         return mPangoFont;
     }
 
 protected:
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript,
-                               bool aPreferPlatformShaping);
-
-    bool InitGlyphRunWithPango(gfxTextRun *aTextRun,
-                                 const PRUnichar *aString,
-                                 PRUint32 aRunStart, PRUint32 aRunLength,
-                                 PangoScript aScript);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString,
+                           bool aPreferPlatformShaping);
+
+    bool InitGlyphRunWithPango(gfxShapedWord *aTextRun,
+                               const PRUnichar *aString);
 
 private:
-    static already_AddRefed<gfxFcFont> GetOrMakeFont(FcPattern *aPattern);
     gfxFcFont(cairo_scaled_font_t *aCairoFont, gfxFcFontEntry *aFontEntry,
               const gfxFontStyle *aFontStyle);
 
     void MakePangoFont();
 
     PangoFont *mPangoFont;
 
     // key for locating a gfxFcFont corresponding to a cairo_scaled_font
@@ -1199,26 +1195,27 @@ public:
     {
         bool waitForUserFont;
         mFcFontSet = SortPreferredFonts(waitForUserFont);
         mWaitingForUserFont = waitForUserFont;
     }
 
     // A reference is held by the FontSet.
     // The caller may add a ref to keep the font alive longer than the FontSet.
-    gfxFcFont *GetFontAt(PRUint32 i)
+    gfxFcFont *GetFontAt(PRUint32 i, const gfxFontStyle *aFontStyle)
     {
         if (i >= mFonts.Length() || !mFonts[i].mFont) { 
             // GetFontPatternAt sets up mFonts
             FcPattern *fontPattern = GetFontPatternAt(i);
             if (!fontPattern)
                 return NULL;
 
             mFonts[i].mFont =
-                gfxFcFont::GetOrMakeFont(mSortPattern, fontPattern);
+                gfxFcFont::GetOrMakeFont(mSortPattern, fontPattern,
+                                         aFontStyle);
         }
         return mFonts[i].mFont;
     }
 
     FcPattern *GetFontPatternAt(PRUint32 i);
 
     bool WaitingForUserFont() const {
         return mWaitingForUserFont;
@@ -1940,17 +1937,17 @@ gfxPangoFontGroup::GetFcFamilies(nsTArra
     ForEachFontInternal(mFamilies, aLanguage, true, false, true,
                         FamilyCallback, &data);
 }
 
 gfxFcFont *
 gfxPangoFontGroup::GetBaseFont()
 {
     if (!mFonts[0]) {
-        mFonts[0] = GetBaseFontSet()->GetFontAt(0);
+        mFonts[0] = GetBaseFontSet()->GetFontAt(0, GetStyle());
     }
 
     return static_cast<gfxFcFont*>(mFonts[0].get());
 }
 
 gfxFont *
 gfxPangoFontGroup::GetFontAt(PRInt32 i) {
     // If it turns out to be hard for all clients that cache font
@@ -2046,38 +2043,33 @@ gfxPangoFontGroup::GetFontSet(PangoLangu
 }
 
 already_AddRefed<gfxFont>
 gfxPangoFontGroup::FindFontForChar(PRUint32 aCh, PRUint32 aPrevCh,
                                    PRInt32 aRunScript,
                                    gfxFont *aPrevMatchedFont,
                                    PRUint8 *aMatchType)
 {
-    if (aPrevMatchedFont) {
-        PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(aCh);
-        // If this character is a format character (including join-causers)
-        // or a variation selector, use the same font as the previous
-        // character, regardless of whether it supports the character.
-        // Otherwise the text run will be divided.
-        if ((category == HB_CATEGORY_CONTROL ||
-             category == HB_CATEGORY_FORMAT  ||
-             gfxFontUtils::IsVarSelector(aCh))) {
+    // if this character is a join-control or the previous is a join-causer,
+    // use the same font as the previous range if we can
+    if (gfxFontUtils::IsJoinControl(aCh) || gfxFontUtils::IsJoinCauser(aPrevCh)) {
+        if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) {
             return nsRefPtr<gfxFont>(aPrevMatchedFont).forget();
         }
-
-        // If the previous character is a space or a join-causer and the
-        // previous font supports this character, then use the same font.
-        //
-        // The fonts selected for spaces are usually ignored.  Sticking with
-        // the same font avoids breaking the shaping run.
-        if (aCh == ' ' ||
-            (gfxFontUtils::IsJoinCauser(aPrevCh) &&
-             static_cast<gfxFcFont*>(aPrevMatchedFont)->GetGlyph(aCh))) {
+    }
+
+    // if this character is a variation selector,
+    // use the previous font regardless of whether it supports VS or not.
+    // otherwise the text run will be divided.
+    if (gfxFontUtils::IsVarSelector(aCh)) {
+        if (aPrevMatchedFont) {
             return nsRefPtr<gfxFont>(aPrevMatchedFont).forget();
         }
+        // VS alone. it's meaningless to search different fonts
+        return nsnull;
     }
 
     // The real fonts that fontconfig provides for generic/fallback families
     // depend on the language used, so a different FontSet is used for each
     // language (except for the variation below).
     //
     //   With most fontconfig configurations any real family names prior to a
     //   fontconfig generic with corresponding fonts installed will still lead
@@ -2136,17 +2128,17 @@ gfxPangoFontGroup::FindFontForChar(PRUin
          FcPattern *pattern = fontSet->GetFontPatternAt(i);
          ++i) {
         if (pattern == basePattern) {
             continue; // already checked basePattern
         }
 
         if (HasChar(pattern, aCh)) {
             *aMatchType = gfxTextRange::kFontGroup;
-            return nsRefPtr<gfxFont>(fontSet->GetFontAt(i)).forget();
+            return nsRefPtr<gfxFont>(fontSet->GetFontAt(i, GetStyle())).forget();
         }
     }
 
     return nsnull;
 }
 
 /**
  ** gfxFcFont
@@ -2197,62 +2189,53 @@ gfxFcFont::~gfxFcFont()
     cairo_scaled_font_set_user_data(mScaledFont, &sGfxFontKey, NULL, NULL);
     if (mPangoFont) {
         g_object_remove_toggle_ref(G_OBJECT(mPangoFont),
                                    PangoFontToggleNotify, this);
     }
 }
 
 bool
-gfxFcFont::InitTextRun(gfxContext *aContext,
-                       gfxTextRun *aTextRun,
-                       const PRUnichar *aString,
-                       PRUint32 aRunStart,
-                       PRUint32 aRunLength,
-                       PRInt32 aRunScript,
-                       bool aPreferPlatformShaping)
+gfxFcFont::ShapeWord(gfxContext *aContext,
+                     gfxShapedWord *aShapedWord,
+                     const PRUnichar *aString,
+                     bool aPreferPlatformShaping)
 {
     gfxFcFontEntry *fontEntry = static_cast<gfxFcFontEntry*>(GetFontEntry());
 
 #ifdef MOZ_GRAPHITE
     if (FontCanSupportGraphite()) {
         if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
             if (!mGraphiteShaper) {
                 mGraphiteShaper = new gfxGraphiteShaper(this);
             }
-            if (mGraphiteShaper->InitTextRun(aContext, aTextRun, aString,
-                                             aRunStart, aRunLength,
-                                             aRunScript)) {
+            if (mGraphiteShaper->ShapeWord(aContext, aShapedWord, aString)) {
                 return true;
             }
         }
     }
 #endif
 
-    if (fontEntry->ShouldUseHarfBuzz(aRunScript)) {
+    if (fontEntry->ShouldUseHarfBuzz(aShapedWord->Script())) {
         if (!mHarfBuzzShaper) {
             gfxFT2LockedFace face(this);
             mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
             // Used by gfxHarfBuzzShaper, currently only for kerning
             mFUnitsConvFactor = face.XScale();
         }
-        if (mHarfBuzzShaper->
-            InitTextRun(aContext, aTextRun, aString,
-                        aRunStart, aRunLength, aRunScript)) {
+        if (mHarfBuzzShaper->ShapeWord(aContext, aShapedWord, aString)) {
             return true;
         }
 
         // Wrong font type for HarfBuzz
         fontEntry->SkipHarfBuzz();
         mHarfBuzzShaper = nsnull;
     }
 
-    const PangoScript script = static_cast<PangoScript>(aRunScript);
-    bool ok = InitGlyphRunWithPango(aTextRun,
-                                      aString, aRunStart, aRunLength, script);
+    bool ok = InitGlyphRunWithPango(aShapedWord, aString);
 
     NS_WARN_IF_FALSE(ok, "shaper failed, expect scrambled or missing text");
     return ok;
 }
 
 /* static */ void
 gfxPangoFontGroup::Shutdown()
 {
@@ -2388,17 +2371,18 @@ GetPixelSize(FcPattern *aPattern)
  * The point of this is to record the exact font face for gfxTextRun glyph
  * indices.  The style of this font does not necessarily represent the exact
  * gfxFontStyle used to build the text run.  Notably, the language is not
  * recorded.
  */
 
 /* static */
 already_AddRefed<gfxFcFont>
-gfxFcFont::GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern)
+gfxFcFont::GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern,
+                         const gfxFontStyle *aFontStyle)
 {
     nsAutoRef<FcPattern> renderPattern
         (FcFontRenderPrepare(NULL, aRequestedPattern, aFontPattern));
     cairo_font_face_t *face =
         cairo_ft_font_face_create_for_pattern(renderPattern);
 
     // Reuse an existing font entry if available.
     nsRefPtr<gfxFcFontEntry> fe = gfxFcFontEntry::LookupFontEntry(face);
@@ -2439,53 +2423,40 @@ gfxFcFont::GetOrMakeFont(FcPattern *aReq
                     name.AppendInt(index);
                 }
             }
 
             fe = new gfxSystemFcFontEntry(face, aFontPattern, name);
         }
     }
 
-    cairo_scaled_font_t *cairoFont = CreateScaledFont(renderPattern, face);
-
-    nsRefPtr<gfxFcFont> font = static_cast<gfxFcFont*>
-        (cairo_scaled_font_get_user_data(cairoFont, &sGfxFontKey));
-
+    gfxFontStyle style(*aFontStyle);
+    style.size = GetPixelSize(renderPattern);
+    style.style = gfxFontconfigUtils::GetThebesStyle(renderPattern);
+    style.weight = gfxFontconfigUtils::GetThebesWeight(renderPattern);
+
+    nsRefPtr<gfxFont> font = gfxFontCache::GetCache()->Lookup(fe, &style);
     if (!font) {
-        gfxFloat size = GetPixelSize(renderPattern);
-
-        // Shouldn't actually need to take too much care about the correct
-        // name or style, as size is the only thing expected to be important.
-        PRUint8 style = gfxFontconfigUtils::GetThebesStyle(renderPattern);
-        PRUint16 weight = gfxFontconfigUtils::GetThebesWeight(renderPattern);
-
-        // The LangSet in the FcPattern does not have an order so there is no
-        // one particular language to choose and converting the set to a
-        // string through FcNameUnparse() is more trouble than it's worth.
-        nsIAtom *language = gfxAtoms::en; // TODO: get the correct language?
-        // FIXME: Pass a real stretch based on renderPattern!
-        gfxFontStyle fontStyle(style, weight, NS_FONT_STRETCH_NORMAL,
-                               size, language, 0.0,
-                               true, false,
-                               NS_LITERAL_STRING(""),
-                               NS_LITERAL_STRING(""));
-
         // Note that a file/index pair (or FT_Face) and the gfxFontStyle are
         // not necessarily enough to provide a key that will describe a unique
         // font.  cairoFont contains information from renderPattern, which is a
         // fully resolved pattern from FcFontRenderPrepare.
         // FcFontRenderPrepare takes the requested pattern and the face
         // pattern as input and can modify elements of the resulting pattern
         // that affect rendering but are not included in the gfxFontStyle.
-        font = new gfxFcFont(cairoFont, fe, &fontStyle);
+        cairo_scaled_font_t *cairoFont = CreateScaledFont(renderPattern, face);
+        font = new gfxFcFont(cairoFont, fe, &style);
+        gfxFontCache::GetCache()->AddNew(font);
+        cairo_scaled_font_destroy(cairoFont);
     }
 
-    cairo_scaled_font_destroy(cairoFont);
     cairo_font_face_destroy(face);
-    return font.forget();
+
+    nsRefPtr<gfxFcFont> retval(static_cast<gfxFcFont*>(font.get()));
+    return retval.forget();
 }
 
 static PangoFontMap *
 GetPangoFontMap()
 {
     if (!gPangoFontMap) {
         // This is the same FontMap used by GDK, so that the same
         // PangoCoverage cache is shared.
@@ -2512,17 +2483,17 @@ gfxPangoFontGroup::GetBaseFontSet()
 
     mSizeAdjustFactor = 1.0; // will be adjusted below if necessary
     nsAutoRef<FcPattern> pattern;
     nsRefPtr<gfxFcFontSet> fontSet =
         MakeFontSet(mPangoLanguage, mSizeAdjustFactor, &pattern);
 
     double size = GetPixelSize(pattern);
     if (size != 0.0 && mStyle.sizeAdjust != 0.0) {
-        gfxFcFont *font = fontSet->GetFontAt(0);
+        gfxFcFont *font = fontSet->GetFontAt(0, GetStyle());
         if (font) {
             const gfxFont::Metrics& metrics = font->GetMetrics();
 
             // The factor of 0.1 ensures that xHeight is sane so fonts don't
             // become huge.  Strictly ">" ensures that xHeight and emHeight are
             // not both zero.
             if (metrics.xHeight > 0.1 * metrics.emHeight) {
                 mSizeAdjustFactor =
@@ -2742,141 +2713,93 @@ CreateScaledFont(FcPattern *aPattern, ca
 
     cairo_font_options_destroy(fontOptions);
 
     NS_ASSERTION(cairo_scaled_font_status(scaledFont) == CAIRO_STATUS_SUCCESS,
                  "Failed to create scaled font");
     return scaledFont;
 }
 
-static void
-SetupClusterBoundaries(gfxTextRun* aTextRun, const gchar *aUTF8, PRUint32 aUTF8Length,
-                       PRUint32 aUTF16Offset, PangoAnalysis *aAnalysis)
-{
-    if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) {
-        // 8-bit text doesn't have clusters.
-        // XXX is this true in all languages???
-        // behdad: don't think so.  Czech for example IIRC has a
-        // 'ch' grapheme.
-        return;
-    }
-
-    // Pango says "the array of PangoLogAttr passed in must have at least N+1
-    // elements, if there are N characters in the text being broken".
-    // Could use g_utf8_strlen(aUTF8, aUTF8Length) + 1 but the memory savings
-    // may not be worth the call.
-    nsAutoTArray<PangoLogAttr,2000> buffer;
-    if (!buffer.AppendElements(aUTF8Length + 1))
-        return;
-
-    pango_break(aUTF8, aUTF8Length, aAnalysis,
-                buffer.Elements(), buffer.Length());
-
-    const gchar *p = aUTF8;
-    const gchar *end = aUTF8 + aUTF8Length;
-    const PangoLogAttr *attr = buffer.Elements();
-    gfxTextRun::CompressedGlyph g;
-    while (p < end) {
-        if (!attr->is_cursor_position) {
-            aTextRun->SetGlyphs(aUTF16Offset, g.SetComplex(false, true, 0), nsnull);
-        }
-        ++aUTF16Offset;
-        
-        gunichar ch = g_utf8_get_char(p);
-        NS_ASSERTION(ch != 0, "Shouldn't have NUL in pango_break");
-        NS_ASSERTION(!IS_SURROGATE(ch), "Shouldn't have surrogates in UTF8");
-        if (ch >= 0x10000) {
-            // set glyph info for the UTF-16 low surrogate
-            aTextRun->SetGlyphs(aUTF16Offset, g.SetComplex(false, false, 0), nsnull);
-            ++aUTF16Offset;
-        }
-        // We produced this utf8 so we don't need to worry about malformed stuff
-        p = g_utf8_next_char(p);
-        ++attr;
-    }
-}
-
 static PRInt32
 ConvertPangoToAppUnits(PRInt32 aCoordinate, PRUint32 aAppUnitsPerDevUnit)
 {
     PRInt64 v = (PRInt64(aCoordinate)*aAppUnitsPerDevUnit + PANGO_SCALE/2)/PANGO_SCALE;
     return PRInt32(v);
 }
 
 /**
  * Given a run of Pango 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 nsresult
 SetGlyphsForCharacterGroup(const PangoGlyphInfo *aGlyphs, PRUint32 aGlyphCount,
-                           gfxTextRun *aTextRun,
+                           gfxShapedWord *aShapedWord,
                            const gchar *aUTF8, PRUint32 aUTF8Length,
                            PRUint32 *aUTF16Offset,
                            PangoGlyphUnit aOverrideSpaceWidth)
 {
     PRUint32 utf16Offset = *aUTF16Offset;
-    PRUint32 textRunLength = aTextRun->GetLength();
-    const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
-    const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
+    PRUint32 wordLength = aShapedWord->Length();
+    const PRUint32 appUnitsPerDevUnit = aShapedWord->AppUnitsPerDevUnit();
 
     // Override the width of a space, but only for spaces that aren't
     // clustered with something else (like a freestanding diacritical mark)
     PangoGlyphUnit width = aGlyphs[0].geometry.width;
     if (aOverrideSpaceWidth && aUTF8[0] == ' ' &&
-        (utf16Offset + 1 == textRunLength ||
-         charGlyphs[utf16Offset].IsClusterStart())) {
+        (utf16Offset + 1 == wordLength ||
+         aShapedWord->IsClusterStart(utf16Offset))) {
         width = aOverrideSpaceWidth;
     }
     PRInt32 advance = ConvertPangoToAppUnits(width, appUnitsPerDevUnit);
 
-    gfxTextRun::CompressedGlyph g;
-    bool atClusterStart = aTextRun->IsClusterStart(utf16Offset);
+    gfxShapedWord::CompressedGlyph g;
+    bool atClusterStart = aShapedWord->IsClusterStart(utf16Offset);
     // See if we fit in the compressed area.
     if (aGlyphCount == 1 && advance >= 0 && atClusterStart &&
         aGlyphs[0].geometry.x_offset == 0 &&
         aGlyphs[0].geometry.y_offset == 0 &&
         !IS_EMPTY_GLYPH(aGlyphs[0].glyph) &&
-        gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
-        gfxTextRun::CompressedGlyph::IsSimpleGlyphID(aGlyphs[0].glyph)) {
-        aTextRun->SetSimpleGlyph(utf16Offset,
-                                 g.SetSimpleGlyph(advance, aGlyphs[0].glyph));
+        gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advance) &&
+        gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(aGlyphs[0].glyph)) {
+        aShapedWord->SetSimpleGlyph(utf16Offset,
+                                    g.SetSimpleGlyph(advance, aGlyphs[0].glyph));
     } else {
-        nsAutoTArray<gfxTextRun::DetailedGlyph,10> detailedGlyphs;
+        nsAutoTArray<gfxShapedWord::DetailedGlyph,10> detailedGlyphs;
         if (!detailedGlyphs.AppendElements(aGlyphCount))
             return NS_ERROR_OUT_OF_MEMORY;
 
-        PRInt32 direction = aTextRun->IsRightToLeft() ? -1 : 1;
+        PRInt32 direction = aShapedWord->IsRightToLeft() ? -1 : 1;
         PRUint32 pangoIndex = direction > 0 ? 0 : aGlyphCount - 1;
         PRUint32 detailedIndex = 0;
         for (PRUint32 i = 0; i < aGlyphCount; ++i) {
             const PangoGlyphInfo &glyph = aGlyphs[pangoIndex];
             pangoIndex += direction;
             // The zero width characters return empty glyph ID at
             // shaping; we should skip these.
             if (IS_EMPTY_GLYPH(glyph.glyph))
                 continue;
 
-            gfxTextRun::DetailedGlyph *details = &detailedGlyphs[detailedIndex];
+            gfxShapedWord::DetailedGlyph *details = &detailedGlyphs[detailedIndex];
             ++detailedIndex;
 
             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;
         }
         g.SetComplex(atClusterStart, true, detailedIndex);
-        aTextRun->SetGlyphs(utf16Offset, g, detailedGlyphs.Elements());
+        aShapedWord->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,
@@ -2891,32 +2814,33 @@ SetGlyphsForCharacterGroup(const PangoGl
                      "Invalid character detected");
         ++utf16Offset;
 
         // We produced this UTF8 so we don't need to worry about malformed stuff
         p = g_utf8_next_char(p);
         if (p >= end)
             break;
 
-        if (utf16Offset >= textRunLength) {
+        if (utf16Offset >= wordLength) {
             NS_ERROR("Someone has added too many glyphs!");
             return NS_ERROR_FAILURE;
         }
 
-        g.SetComplex(aTextRun->IsClusterStart(utf16Offset), false, 0);
-        aTextRun->SetGlyphs(utf16Offset, g, nsnull);
+        g.SetComplex(aShapedWord->IsClusterStart(utf16Offset), false, 0);
+        aShapedWord->SetGlyphs(utf16Offset, g, nsnull);
     }
     *aUTF16Offset = utf16Offset;
     return NS_OK;
 }
 
 static nsresult
-SetGlyphs(gfxTextRun *aTextRun, const gchar *aUTF8, PRUint32 aUTF8Length,
+SetGlyphs(gfxShapedWord *aShapedWord, const gchar *aUTF8, PRUint32 aUTF8Length,
           PRUint32 *aUTF16Offset, PangoGlyphString *aGlyphs,
-          PangoGlyphUnit aOverrideSpaceWidth)
+          PangoGlyphUnit aOverrideSpaceWidth,
+          gfxFont *aFont)
 {
     gint numGlyphs = aGlyphs->num_glyphs;
     PangoGlyphInfo *glyphs = aGlyphs->glyphs;
     const gint *logClusters = aGlyphs->log_clusters;
     // We cannot make any assumptions about the order of glyph clusters
     // provided by pango_shape (see 375864), so we work through the UTF8 text
     // and process the glyph clusters in logical order.
 
@@ -2939,32 +2863,32 @@ SetGlyphs(gfxTextRun *aTextRun, const gc
             lastCluster = thisCluster;
             NS_ASSERTION(0 <= thisCluster && thisCluster < gint(aUTF8Length),
                          "garbage from pango_shape - this is bad");
             logGlyphs[thisCluster] = glyphIndex;
         }
     }
 
     PRUint32 utf16Offset = *aUTF16Offset;
-    PRUint32 textRunLength = aTextRun->GetLength();
+    PRUint32 wordLength = aShapedWord->Length();
     utf8Index = 0;
     // The next glyph cluster in logical order. 
     gint nextGlyphClusterStart = logGlyphs[utf8Index];
     NS_ASSERTION(nextGlyphClusterStart >= 0, "No glyphs! - NUL in string?");
     while (utf8Index < aUTF8Length) {
-        if (utf16Offset >= textRunLength) {
+        if (utf16Offset >= wordLength) {
           NS_ERROR("Someone has added too many glyphs!");
           return NS_ERROR_FAILURE;
         }
         gint glyphClusterStart = nextGlyphClusterStart;
         // Find the utf8 text associated with this glyph cluster.
         PRUint32 clusterUTF8Start = utf8Index;
-        // Check we are consistent with pango_break data.
-        NS_ASSERTION(aTextRun->GetCharacterGlyphs()->IsClusterStart(),
-                     "Glyph cluster not aligned on character cluster.");
+        // Check whether we are consistent with pango_break data.
+        NS_WARN_IF_FALSE(aShapedWord->IsClusterStart(utf16Offset),
+                         "Glyph cluster not aligned on character cluster.");
         do {
             ++utf8Index;
             nextGlyphClusterStart = logGlyphs[utf8Index];
         } while (nextGlyphClusterStart < 0);
         const gchar *clusterUTF8 = &aUTF8[clusterUTF8Start];
         PRUint32 clusterUTF8Length = utf8Index - clusterUTF8Start;
 
         bool haveMissingGlyph = false;
@@ -2979,92 +2903,91 @@ SetGlyphs(gfxTextRun *aTextRun, const gc
                 haveMissingGlyph = true;
             }
             glyphIndex++;
         } while (glyphIndex < numGlyphs && 
                  logClusters[glyphIndex] == gint(clusterUTF8Start));
 
         nsresult rv;
         if (haveMissingGlyph) {
-            SetMissingGlyphs(aTextRun, clusterUTF8, clusterUTF8Length,
-                             &utf16Offset);
+            SetMissingGlyphs(aShapedWord, clusterUTF8, clusterUTF8Length,
+                             &utf16Offset, aFont);
         } else {
             rv = SetGlyphsForCharacterGroup(&glyphs[glyphClusterStart],
                                             glyphIndex - glyphClusterStart,
-                                            aTextRun,
+                                            aShapedWord,
                                             clusterUTF8, clusterUTF8Length,
                                             &utf16Offset, aOverrideSpaceWidth);
             NS_ENSURE_SUCCESS(rv,rv);
         }
     }
     *aUTF16Offset = utf16Offset;
     return NS_OK;
 }
 
 static void
-SetMissingGlyphs(gfxTextRun *aTextRun, const gchar *aUTF8,
-                 PRUint32 aUTF8Length, PRUint32 *aUTF16Offset)
+SetMissingGlyphs(gfxShapedWord *aShapedWord, const gchar *aUTF8,
+                 PRUint32 aUTF8Length, PRUint32 *aUTF16Offset,
+                 gfxFont *aFont)
 {
     PRUint32 utf16Offset = *aUTF16Offset;
-    PRUint32 textRunLength = aTextRun->GetLength();
+    PRUint32 wordLength = aShapedWord->Length();
     for (PRUint32 index = 0; index < aUTF8Length;) {
-        if (utf16Offset >= textRunLength) {
+        if (utf16Offset >= wordLength) {
             NS_ERROR("Someone has added too many glyphs!");
             break;
         }
         gunichar ch = g_utf8_get_char(aUTF8 + index);
-        aTextRun->SetMissingGlyph(utf16Offset, ch);
+        aShapedWord->SetMissingGlyph(utf16Offset, ch, aFont);
 
         ++utf16Offset;
         NS_ASSERTION(!IS_SURROGATE(ch), "surrogates should not appear in UTF8");
         if (ch >= 0x10000)
             ++utf16Offset;
         // We produced this UTF8 so we don't need to worry about malformed stuff
         index = g_utf8_next_char(aUTF8 + index) - aUTF8;
     }
 
     *aUTF16Offset = utf16Offset;
 }
 
 static void
-InitGlyphRunWithPangoAnalysis(gfxTextRun *aTextRun,
+InitGlyphRunWithPangoAnalysis(gfxShapedWord *aShapedWord,
                               const gchar *aUTF8, PRUint32 aUTF8Length,
-                              PRUint32 *aUTF16Offset,
                               PangoAnalysis *aAnalysis,
-                              PangoGlyphUnit aOverrideSpaceWidth)
+                              PangoGlyphUnit aOverrideSpaceWidth,
+                              gfxFont *aFont)
 {
-    PRUint32 utf16Offset = *aUTF16Offset;
+    PRUint32 utf16Offset = 0;
     PangoGlyphString *glyphString = pango_glyph_string_new();
 
     const gchar *p = aUTF8;
     const gchar *end = p + aUTF8Length;
     while (p < end) {
         if (*p == 0) {
-            aTextRun->SetMissingGlyph(utf16Offset, 0);
+            aShapedWord->SetMissingGlyph(utf16Offset, 0, aFont);
             ++p;
             ++utf16Offset;
             continue;
         }
 
         // It's necessary to loop over pango_shape as it treats
         // NULs as string terminators
         const gchar *text = p;
         do {
             ++p;
         } while(p < end && *p != 0);
         gint len = p - text;
 
         pango_shape(text, len, aAnalysis, glyphString);
-        SetupClusterBoundaries(aTextRun, text, len, utf16Offset, aAnalysis);
-        SetGlyphs(aTextRun, text, len, &utf16Offset, glyphString,
-                  aOverrideSpaceWidth);
+        SetGlyphs(aShapedWord, text, len, &utf16Offset, glyphString,
+                  aOverrideSpaceWidth, aFont);
     }
 
     pango_glyph_string_free(glyphString);
-    *aUTF16Offset = utf16Offset;
 }
 
 // PangoAnalysis is part of Pango's ABI but over time extra fields have been
 // inserted into padding.  This union is used so that the code here can be
 // compiled against older Pango versions but run against newer versions.
 typedef union {
     PangoAnalysis pango;
     // This struct matches PangoAnalysis from Pango version
@@ -3078,49 +3001,50 @@ typedef union {
         guint8 flags;
         guint8 script; /* PangoScript */
         PangoLanguage *language;
         GSList *extra_attrs;
     } local;
 } PangoAnalysisUnion;
 
 bool
-gfxFcFont::InitGlyphRunWithPango(gfxTextRun *aTextRun,
-                                 const PRUnichar *aString,
-                                 PRUint32 aRunStart, PRUint32 aRunLength,
-                                 PangoScript aScript)
+gfxFcFont::InitGlyphRunWithPango(gfxShapedWord *aShapedWord,
+                                 const PRUnichar *aString)
 {
-    NS_ConvertUTF16toUTF8 utf8(aString + aRunStart, aRunLength);
+    const PangoScript script = static_cast<PangoScript>(aShapedWord->Script());
+    NS_ConvertUTF16toUTF8 utf8(aString, aShapedWord->Length());
 
     PangoFont *font = GetPangoFont();
-    gfxPangoFontGroup *fontGroup =
-        static_cast<gfxPangoFontGroup*>(aTextRun->GetFontGroup());
 
     hb_language_t languageOverride = NULL;
-    if (fontGroup->GetStyle()->languageOverride) {
+    if (GetStyle()->languageOverride) {
         languageOverride =
-            hb_ot_tag_to_language(fontGroup->GetStyle()->languageOverride);
+            hb_ot_tag_to_language(GetStyle()->languageOverride);
     } else if (GetFontEntry()->mLanguageOverride) {
         languageOverride =
             hb_ot_tag_to_language(GetFontEntry()->mLanguageOverride);
     }
 
     PangoLanguage *language;
     if (languageOverride) {
         language =
             pango_language_from_string(hb_language_to_string(languageOverride));
     } else {
+#if 0 // FIXME ??
         language = fontGroup->GetPangoLanguage();
+#endif
+        // FIXME: should probably cache this in the gfxFcFont
+        language = GuessPangoLanguage(GetStyle()->language);
+
         // The language that we have here is often not as good an indicator for
         // the run as the script.  This is not so important for the PangoMaps
         // here as all the default Pango shape and lang engines are selected
         // by script only (not language) anyway, but may be important in the
         // PangoAnalysis as the shaper sometimes accesses language-specific
         // tables.
-        const PangoScript script = static_cast<PangoScript>(aScript);
         PangoLanguage *scriptLang;
         if ((!language ||
              !pango_language_includes_script(language, script)) &&
             (scriptLang = pango_script_get_sample_language(script))) {
             language = scriptLang;
         }
     }
 
@@ -3136,17 +3060,17 @@ gfxFcFont::InitGlyphRunWithPango(gfxText
         g_quark_from_static_string(PANGO_RENDER_TYPE_FC);
     PangoMap *shapeMap = pango_find_map(language, engineShapeId, renderFcId);
     if (!shapeMap) {
         return false;
     }
 
     // The preferred shape engine for language and script
     PangoEngineShape *shapeEngine =
-        PANGO_ENGINE_SHAPE(pango_map_get_engine(shapeMap, aScript));
+        PANGO_ENGINE_SHAPE(pango_map_get_engine(shapeMap, script));
     if (!shapeEngine) {
         return false;
     }
 
     PangoEngineShapeClass *shapeClass = static_cast<PangoEngineShapeClass*>
         (g_type_class_peek(PANGO_TYPE_ENGINE_SHAPE));
 
     // The |covers| method in the PangoEngineShape base class, which is the
@@ -3157,17 +3081,17 @@ gfxFcFont::InitGlyphRunWithPango(gfxText
     //
     // With SIL Graphite shapers, however, |covers| also checks that the font
     // is a Graphite font.  (bug 397860)
     if (!shapeClass ||
         PANGO_ENGINE_SHAPE_GET_CLASS(shapeEngine)->covers != shapeClass->covers)
     {
         GSList *exact_engines;
         GSList *fallback_engines;
-        pango_map_get_engines(shapeMap, aScript,
+        pango_map_get_engines(shapeMap, script,
                               &exact_engines, &fallback_engines);
 
         GSList *engines = g_slist_concat(exact_engines, fallback_engines);
         for (GSList *link = engines; link; link = link->next) {
             PangoEngineShape *engine = PANGO_ENGINE_SHAPE(link->data);
             PangoCoverageLevel (*covers)(PangoEngineShape*, PangoFont*,
                                          PangoLanguage*, gunichar) =
                 PANGO_ENGINE_SHAPE_GET_CLASS(shapeEngine)->covers;
@@ -3184,45 +3108,44 @@ gfxFcFont::InitGlyphRunWithPango(gfxText
 
     PangoAnalysisUnion analysis;
     memset(&analysis, 0, sizeof(analysis));
 
     // For pango_shape
     analysis.local.shape_engine = shapeEngine;
     // For pango_break
     analysis.local.lang_engine =
-        PANGO_ENGINE_LANG(pango_map_get_engine(langMap, aScript));
+        PANGO_ENGINE_LANG(pango_map_get_engine(langMap, script));
 
     analysis.local.font = font;
-    analysis.local.level = aTextRun->IsRightToLeft() ? 1 : 0;
+    analysis.local.level = aShapedWord->IsRightToLeft() ? 1 : 0;
     // gravity and flags are used in Pango 1.14.10 and newer.
     //
     // PANGO_GRAVITY_SOUTH is what we want for upright horizontal text.  The
     // constant is not available when compiling with older Pango versions, but
     // is zero so the zero memset initialization is sufficient.
     //
     // Pango uses non-zero flags for vertical gravities only
     // (up to version 1.28 at least), so using zero is fine for flags too.
 #if 0
     analysis.local.gravity = PANGO_GRAVITY_SOUTH;
     analysis.local.flags = 0;
 #endif
     // Only used in Pango 1.16.5 and newer.
-    analysis.local.script = aScript;
+    analysis.local.script = script;
 
     analysis.local.language = language;
     // Non-font attributes.  Not used here.
     analysis.local.extra_attrs = NULL;
 
     PangoGlyphUnit spaceWidth =
         moz_pango_units_from_double(GetMetrics().spaceWidth);
 
-    PRUint32 utf16Offset = aRunStart;
-    InitGlyphRunWithPangoAnalysis(aTextRun, utf8.get(), utf8.Length(),
-                                  &utf16Offset, &analysis.pango, spaceWidth);
+    InitGlyphRunWithPangoAnalysis(aShapedWord, utf8.get(), utf8.Length(),
+                                  &analysis.pango, spaceWidth, this);
     return true;
 }
 
 /* static */
 PangoLanguage *
 GuessPangoLanguage(nsIAtom *aLanguage)
 {
     if (!aLanguage)
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -56,18 +56,16 @@
 #elif defined(ANDROID)
 #include "gfxAndroidPlatform.h"
 #endif
 
 #include "gfxAtoms.h"
 #include "gfxPlatformFontList.h"
 #include "gfxContext.h"
 #include "gfxImageSurface.h"
-#include "gfxTextRunCache.h"
-#include "gfxTextRunWordCache.h"
 #include "gfxUserFontSet.h"
 #include "gfxUnicodeProperties.h"
 #include "harfbuzz/hb-unicode.h"
 #ifdef MOZ_GRAPHITE
 #include "gfxGraphiteShaper.h"
 #endif
 
 #include "nsUnicodeRange.h"
@@ -152,19 +150,22 @@ SRGBOverrideObserver::Observe(nsISupport
 
 #define GFX_PREF_HARFBUZZ_SCRIPTS "gfx.font_rendering.harfbuzz.scripts"
 #define HARFBUZZ_SCRIPTS_DEFAULT  gfxUnicodeProperties::SHAPING_DEFAULT
 
 #ifdef MOZ_GRAPHITE
 #define GFX_PREF_GRAPHITE_SHAPING "gfx.font_rendering.graphite.enabled"
 #endif
 
+#define BIDI_NUMERAL_PREF "bidi.numeral"
+
 static const char* kObservedPrefs[] = {
     "gfx.downloadable_fonts.",
     "gfx.font_rendering.",
+    "bidi.numeral",
     nsnull
 };
 
 class FontPrefsObserver : public nsIObserver
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
@@ -230,16 +231,17 @@ gfxPlatform::gfxPlatform()
   : mAzureBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
 {
     mUseHarfBuzzScripts = UNINITIALIZED_VALUE;
     mAllowDownloadableFonts = UNINITIALIZED_VALUE;
     mDownloadableFontsSanitize = UNINITIALIZED_VALUE;
 #ifdef MOZ_GRAPHITE
     mGraphiteShapingEnabled = UNINITIALIZED_VALUE;
 #endif
+    mBidiNumeralOption = UNINITIALIZED_VALUE;
 }
 
 gfxPlatform*
 gfxPlatform::GetPlatform()
 {
     if (!gPlatform) {
         Init();
     }
@@ -307,26 +309,16 @@ gfxPlatform::Init()
         NS_RUNTIMEABORT("Could not initialize mScreenReferenceSurface");
     }
 
     rv = gfxFontCache::Init();
     if (NS_FAILED(rv)) {
         NS_RUNTIMEABORT("Could not initialize gfxFontCache");
     }
 
-    rv = gfxTextRunWordCache::Init();
-    if (NS_FAILED(rv)) {
-        NS_RUNTIMEABORT("Could not initialize gfxTextRunWordCache");
-    }
-
-    rv = gfxTextRunCache::Init();
-    if (NS_FAILED(rv)) {
-        NS_RUNTIMEABORT("Could not initialize gfxTextRunCache");
-    }
-
     /* Pref migration hook. */
     MigratePrefs();
 
     /* Create and register our CMS Override observer. */
     gPlatform->mSRGBOverrideObserver = new SRGBOverrideObserver();
     Preferences::AddWeakObserver(gPlatform->mSRGBOverrideObserver, "gfx.color_management.force_srgb");
 
     gPlatform->mFontPrefsObserver = new FontPrefsObserver();
@@ -338,18 +330,16 @@ gfxPlatform::Init()
         = do_CreateInstance("@mozilla.org/gfx/init;1");
 }
 
 void
 gfxPlatform::Shutdown()
 {
     // These may be called before the corresponding subsystems have actually
     // started up. That's OK, they can handle it.
-    gfxTextRunCache::Shutdown();
-    gfxTextRunWordCache::Shutdown();
     gfxFontCache::Shutdown();
     gfxFontGroup::Shutdown();
 #ifdef MOZ_GRAPHITE
     gfxGraphiteShaper::Shutdown();
 #endif
 #if defined(XP_MACOSX) || defined(XP_WIN) // temporary, until this is implemented on others
     gfxPlatformFontList::Shutdown();
 #endif
@@ -1255,136 +1245,47 @@ gfxPlatform::SetupClusterBoundaries(gfxT
         // XXX is this true in all languages???
         // behdad: don't think so.  Czech for example IIRC has a
         // 'ch' grapheme.
         // jfkthame: but that's not expected to behave as a grapheme cluster
         // for selection/editing/etc.
         return;
     }
 
-    gfxTextRun::CompressedGlyph extendCluster;
-    extendCluster.SetComplex(false, true, 0);
-
-    PRUint32 i, length = aTextRun->GetLength();
-    gfxUnicodeProperties::HSType hangulState = gfxUnicodeProperties::HST_NONE;
-
-    for (i = 0; i < length; ++i) {
-        bool surrogatePair = false;
-        PRUint32 ch = aString[i];
-        if (NS_IS_HIGH_SURROGATE(ch) &&
-            i < length - 1 && NS_IS_LOW_SURROGATE(aString[i+1]))
-        {
-            ch = SURROGATE_TO_UCS4(ch, aString[i+1]);
-            surrogatePair = true;
-        }
-
-        PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(ch);
-        gfxUnicodeProperties::HSType hangulType = gfxUnicodeProperties::HST_NONE;
-
-        // combining marks extend the cluster
-        if ((category >= HB_CATEGORY_COMBINING_MARK &&
-             category <= HB_CATEGORY_NON_SPACING_MARK) ||
-            (ch >= 0x200c && ch <= 0x200d) || // ZWJ, ZWNJ
-            (ch >= 0xff9e && ch <= 0xff9f))   // katakana sound marks
-        {
-            if (i > 0) {
-                aTextRun->SetGlyphs(i, extendCluster, nsnull);
-            }
-        } else if (category == HB_CATEGORY_OTHER_LETTER) {
-            // handle special cases in Letter_Other category
-#if 0
-            // Currently disabled. This would follow the UAX#29 specification
-            // for extended grapheme clusters, but this is not favored by
-            // Thai users, at least for editing behavior.
-            // See discussion of equivalent Pango issue in bug 474068 and
-            // upstream at https://bugzilla.gnome.org/show_bug.cgi?id=576156.
+    gfxShapedWord::SetupClusterBoundaries(aTextRun->GetCharacterGlyphs(),
+                                          aString, aTextRun->GetLength());
+}
 
-            if ((ch & ~0xff) == 0x0e00) {
-                // specific Thai & Lao (U+0Exx) chars that extend the cluster
-                if ( ch == 0x0e30 ||
-                    (ch >= 0x0e32 && ch <= 0x0e33) ||
-                     ch == 0x0e45 ||
-                     ch == 0x0eb0 ||
-                    (ch >= 0x0eb2 && ch <= 0x0eb3))
-                {
-                    if (i > 0) {
-                        aTextRun->SetGlyphs(i, extendCluster, nsnull);
-                    }
-                }
-                else if ((ch >= 0x0e40 && ch <= 0x0e44) ||
-                         (ch >= 0x0ec0 && ch <= 0x0ec4))
-                {
-                    // characters that are prepended to the following cluster
-                    if (i < length - 1) {
-                        aTextRun->SetGlyphs(i+1, extendCluster, nsnull);
-                    }
-                }
-            } else
-#endif
-            if ((ch & ~0xff) == 0x1100 ||
-                (ch >= 0xa960 && ch <= 0xa97f) ||
-                (ch >= 0xac00 && ch <= 0xd7ff))
-            {
-                // no break within Hangul syllables
-                hangulType = gfxUnicodeProperties::GetHangulSyllableType(ch);
-                switch (hangulType) {
-                case gfxUnicodeProperties::HST_L:
-                case gfxUnicodeProperties::HST_LV:
-                case gfxUnicodeProperties::HST_LVT:
-                    if (hangulState == gfxUnicodeProperties::HST_L) {
-                        aTextRun->SetGlyphs(i, extendCluster, nsnull);
-                    }
-                    break;
-                case gfxUnicodeProperties::HST_V:
-                    if ( (hangulState != gfxUnicodeProperties::HST_NONE) &&
-                        !(hangulState & gfxUnicodeProperties::HST_T))
-                    {
-                        aTextRun->SetGlyphs(i, extendCluster, nsnull);
-                    }
-                    break;
-                case gfxUnicodeProperties::HST_T:
-                    if (hangulState & (gfxUnicodeProperties::HST_V |
-                                       gfxUnicodeProperties::HST_T))
-                    {
-                        aTextRun->SetGlyphs(i, extendCluster, nsnull);
-                    }
-                    break;
-                default:
-                    break;
-                }
-            }
-        }
-
-        if (surrogatePair) {
-            ++i;
-            aTextRun->SetGlyphs(i, extendCluster, nsnull);
-        }
-
-        hangulState = hangulType;
+PRInt32
+gfxPlatform::GetBidiNumeralOption()
+{
+    if (mBidiNumeralOption == UNINITIALIZED_VALUE) {
+        mBidiNumeralOption = Preferences::GetInt(BIDI_NUMERAL_PREF, 0);
     }
+    return mBidiNumeralOption;
 }
 
 void
 gfxPlatform::FontsPrefsChanged(const char *aPref)
 {
     NS_ASSERTION(aPref != nsnull, "null preference");
     if (!strcmp(GFX_DOWNLOADABLE_FONTS_ENABLED, aPref)) {
         mAllowDownloadableFonts = UNINITIALIZED_VALUE;
     } else if (!strcmp(GFX_DOWNLOADABLE_FONTS_SANITIZE, aPref)) {
         mDownloadableFontsSanitize = UNINITIALIZED_VALUE;
 #ifdef MOZ_GRAPHITE
     } else if (!strcmp(GFX_PREF_GRAPHITE_SHAPING, aPref)) {
         mGraphiteShapingEnabled = UNINITIALIZED_VALUE;
-        gfxTextRunWordCache::Flush();
         gfxFontCache::GetCache()->AgeAllGenerations();
 #endif
     } else if (!strcmp(GFX_PREF_HARFBUZZ_SCRIPTS, aPref)) {
         mUseHarfBuzzScripts = UNINITIALIZED_VALUE;
-        gfxTextRunWordCache::Flush();
         gfxFontCache::GetCache()->AgeAllGenerations();
+    } else if (!strcmp(BIDI_NUMERAL_PREF, aPref)) {
+        mBidiNumeralOption = UNINITIALIZED_VALUE;
     }
 }
 
 
 PRLogModuleInfo*
 gfxPlatform::GetLog(eGfxLog aWhichLog)
 {
 #ifdef PR_LOGGING
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -406,16 +406,18 @@ public:
 
     /**
      * Return sRGBA -> output device transform.
      */
     static qcms_transform* GetCMSRGBATransform();
 
     virtual void FontsPrefsChanged(const char *aPref);
 
+    PRInt32 GetBidiNumeralOption();
+
     /**
      * Returns a 1x1 surface that can be used to create graphics contexts
      * for measuring text etc as if they will be rendered to the screen
      */
     gfxASurface* ScreenReferenceSurface() { return mScreenReferenceSurface; }
 
     virtual gfxImageFormat GetOffscreenFormat()
     { return gfxASurface::FormatFromContent(gfxASurface::CONTENT_COLOR); }
@@ -433,16 +435,18 @@ protected:
                             eFontPrefLang aCharLang, eFontPrefLang aPageLang);
                                                
     PRInt8  mAllowDownloadableFonts;
     PRInt8  mDownloadableFontsSanitize;
 #ifdef MOZ_GRAPHITE
     PRInt8  mGraphiteShapingEnabled;
 #endif
 
+    PRInt8  mBidiNumeralOption;
+
     // which scripts should be shaped with harfbuzz
     PRInt32 mUseHarfBuzzScripts;
 
 private:
     virtual qcms_profile* GetPlatformCMSOutputProfile();
 
     nsRefPtr<gfxASurface> mScreenReferenceSurface;
     nsTArray<PRUint32> mCJKPrefLangs;
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -65,17 +65,16 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifdef MOZ_LOGGING
 #define FORCE_PR_LOG /* Allow logging in the release build */
 #endif
 #include "prlog.h"
 
 #include "gfxPlatformFontList.h"
-#include "gfxTextRunWordCache.h"
 
 #include "nsUnicharUtils.h"
 #include "nsUnicodeRange.h"
 #include "gfxUnicodeProperties.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
deleted file mode 100644
--- a/gfx/thebes/gfxTextRunCache.cpp
+++ /dev/null
@@ -1,141 +0,0 @@
-/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Foundation code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2006
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Vladimir Vukicevic <vladimir@pobox.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#include "gfxTextRunCache.h"
-#include "gfxTextRunWordCache.h"
-
-#include "nsExpirationTracker.h"
-
-enum {
-    TEXTRUN_TIMEOUT_MS = 10 * 1000 // textruns expire after 3 * this period
-};
-
-/*
- * Cache textruns and expire them after a period of no use
- */
-class TextRunExpiringCache : public nsExpirationTracker<gfxTextRun,3> {
-public:
-    TextRunExpiringCache()
-        : nsExpirationTracker<gfxTextRun,3>(TEXTRUN_TIMEOUT_MS) {}
-    ~TextRunExpiringCache() {
-        AgeAllGenerations();
-    }
-
-    // This gets called when the timeout has expired on a gfxTextRun
-    virtual void NotifyExpired(gfxTextRun *aTextRun) {
-        RemoveObject(aTextRun);
-        gfxTextRunWordCache::RemoveTextRun(aTextRun);
-        delete aTextRun;
-    }
-};
-
-static TextRunExpiringCache *gTextRunCache = nsnull;
-
-gfxTextRun *
-gfxTextRunCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                             gfxFontGroup *aFontGroup,
-                             const gfxTextRunFactory::Parameters* aParams,
-                             PRUint32 aFlags)
-{
-    if (!gTextRunCache)
-        return nsnull;
-    return gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
-        aParams, aFlags);
-}
-
-gfxTextRun *
-gfxTextRunCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                             gfxFontGroup *aFontGroup,
-                             gfxContext *aRefContext,
-                             PRUint32 aAppUnitsPerDevUnit,
-                             PRUint32 aFlags)
-{
-    if (!gTextRunCache)
-        return nsnull;
-    gfxTextRunFactory::Parameters params = {
-        aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit
-    };
-    return gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
-        &params, aFlags);
-}
-
-gfxTextRun *
-gfxTextRunCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
-                             gfxFontGroup *aFontGroup,
-                             gfxContext *aRefContext,
-                             PRUint32 aAppUnitsPerDevUnit,
-                             PRUint32 aFlags)
-{
-    if (!gTextRunCache)
-        return nsnull;
-    gfxTextRunFactory::Parameters params = {
-        aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit
-    };
-    return gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
-        &params, aFlags);
-}
-
-void
-gfxTextRunCache::ReleaseTextRun(gfxTextRun *aTextRun)
-{
-    if (!aTextRun)
-        return;
-    if (!(aTextRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE)) {
-        delete aTextRun;
-        return;
-    }
-    nsresult rv = gTextRunCache->AddObject(aTextRun);
-    if (NS_FAILED(rv)) {
-        delete aTextRun;
-        return;
-    }
-}
-
-nsresult
-gfxTextRunCache::Init()
-{
-    gTextRunCache = new TextRunExpiringCache();
-    return gTextRunCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-}
-
-void
-gfxTextRunCache::Shutdown()
-{
-    delete gTextRunCache;
-    gTextRunCache = nsnull;
-}
-
deleted file mode 100644
--- a/gfx/thebes/gfxTextRunCache.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Foundation code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2006
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Vladimir Vukicevic <vladimir@pobox.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#ifndef GFX_TEXT_RUN_CACHE_H
-#define GFX_TEXT_RUN_CACHE_H
-
-#include "gfxFont.h"
-
-/**
- * A simple textrun cache for textruns that do not carry state
- * (e.g., actual or potential linebreaks) and do not need complex initialization.
- * The lifetimes of these textruns are managed by the cache (they are auto-expired
- * after a certain period of time).
- */
-class THEBES_API gfxTextRunCache {
-public:
-    /**
-     * Get a textrun for the given text, using a global cache. The textrun
-     * must be released via ReleaseTextRun, not deleted.
-     * Do not set any state in the textrun (e.g. actual or potential linebreaks).
-     * Flags IS_8BIT and IS_ASCII are automatically set appropriately.
-     * Flag IS_PERSISTENT must NOT be set unless aText is guaranteed to live
-     * forever.
-     * The string can contain any characters, invalid ones will be stripped
-     * properly.
-     */
-    static gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                                   gfxFontGroup *aFontGroup,
-                                   gfxContext *aRefContext,
-                                   PRUint32 aAppUnitsPerDevUnit,
-                                   PRUint32 aFlags);
-
-    /**
-     * As above, but allows a full Parameters object to be passed in.
-     */
-    static gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                                   gfxFontGroup *aFontGroup,
-                                   const gfxTextRunFactory::Parameters* aParams,
-                                   PRUint32 aFlags);
-
-    /**
-     * Get a textrun for the given text, using a global cache. The textrun
-     * must be released via ReleaseTextRun, not deleted.
-     * Do not set any state in the textrun (e.g. actual or potential linebreaks).
-     * Flags IS_8BIT, IS_ASCII and HAS_SURROGATES are automatically set
-     * appropriately.
-     * Flag IS_PERSISTENT must NOT be set unless aText is guaranteed to live
-     * forever.
-     * The string can contain any characters, invalid ones will be stripped
-     * properly.
-     */
-    static gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
-                                   gfxFontGroup *aFontGroup,
-                                   gfxContext *aRefContext,
-                                   PRUint32 aAppUnitsPerDevUnit,
-                                   PRUint32 aFlags);
-    
-    /**
-     * Release a previously acquired textrun. Consider using AutoTextRun
-     * instead of calling this.
-     */
-    static void ReleaseTextRun(gfxTextRun *aTextRun);
-
-    class AutoTextRun {
-    public:
-    	AutoTextRun(gfxTextRun *aTextRun) : mTextRun(aTextRun) {}
-    	AutoTextRun() : mTextRun(nsnull) {}
-    	AutoTextRun& operator=(gfxTextRun *aTextRun) {
-            gfxTextRunCache::ReleaseTextRun(mTextRun);
-            mTextRun = aTextRun;
-            return *this;
-        }
-        ~AutoTextRun() {
-            gfxTextRunCache::ReleaseTextRun(mTextRun);
-        }
-        gfxTextRun *get() { return mTextRun; }
-        gfxTextRun *operator->() { return mTextRun; }
-    private:
-        gfxTextRun *mTextRun;
-    };
-
-protected:
-    friend class gfxPlatform;
-
-    static nsresult Init();
-    static void Shutdown();
-};
-
-#endif /* GFX_TEXT_RUN_CACHE_H */
deleted file mode 100644
--- a/gfx/thebes/gfxTextRunWordCache.cpp
+++ /dev/null
@@ -1,1124 +0,0 @@
-/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Foundation code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2006
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Vladimir Vukicevic <vladimir@pobox.com>
- *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
- *   Jonathan Kew <jfkthame@gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#include "gfxTextRunWordCache.h"
-
-#include "nsWeakReference.h"
-#include "nsCRT.h"
-#include "nsIObserver.h"
-
-#include "nsBidiUtils.h"
-#include "mozilla/Preferences.h"
-
-#if defined(XP_UNIX)
-#include <stdint.h>
-#endif
-
-#ifdef DEBUG
-#include <stdio.h>
-#endif
-
-using namespace mozilla;
-
-/**
- * Cache individual "words" (strings delimited by white-space or white-space-like
- * characters that don't involve kerning or ligatures) in textruns.
- *  
- * The characters treated as word boundaries are defined by IsWordBoundary
- * below. The characters are: space, NBSP, and all the characters
- * defined by gfxFontGroup::IsInvalidChar. The latter are all converted
- * to invisible missing glyphs in this code. Thus, this class ensures
- * that none of those invalid characters are ever passed to platform
- * textrun implementations.
- * 
- * Some platforms support marks combining with spaces to form clusters.
- * In such cases we treat "before the space" as a word boundary but
- * "after the space" is not a word boundary; words with a leading space
- * are kept out of the cache. Also, words at the start of text, which start
- * with combining marks that would combine with a space if there was one,
- * are also kept out of the cache.
- */
-
-class TextRunWordCache :
-    public nsIObserver,
-    public nsSupportsWeakReference {
-public:
-    TextRunWordCache() :
-        mBidiNumeral(0) {
-        mCache.Init(100);
-    }
-    ~TextRunWordCache() {
-        Uninit();
-        NS_WARN_IF_FALSE(mCache.Count() == 0, "Textrun cache not empty!");
-    }
-    void Init();
-
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIOBSERVER
-
-    /**
-     * Create a textrun using cached words.
-     * Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
-     * treated as invisible missing.
-     * @param aFlags the flag TEXT_IS_ASCII must be set by the caller,
-     * if applicable
-     * @param aIsInCache if true is returned, then RemoveTextRun must be called
-     * before the textrun changes or dies.
-     */
-    gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                            gfxFontGroup *aFontGroup,
-                            const gfxFontGroup::Parameters *aParams,
-                            PRUint32 aFlags);
-    /**
-     * Create a textrun using cached words.
-     * Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
-     * treated as invisible missing.
-     * @param aFlags the flag TEXT_IS_ASCII must be set by the caller,
-     * if applicable
-     * @param aIsInCache if true is returned, then RemoveTextRun must be called
-     * before the textrun changes or dies.
-     */
-    gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
-                            gfxFontGroup *aFontGroup,
-                            const gfxFontGroup::Parameters *aParams,
-                            PRUint32 aFlags);
-
-    /**
-     * Remove a textrun from the cache. This must be called before aTextRun
-     * is deleted! The text in the textrun must still be valid.
-     */
-    void RemoveTextRun(gfxTextRun *aTextRun);
-
-    /**
-     * Flush all cached runs. Use when a setting change makes them obsolete.
-     */
-    void Flush() {
-        mCache.Clear(); 
-#ifdef DEBUG
-        mGeneration++;
-#endif
-    }
-
-    size_t MaybeSizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf);
-    void ResetSizeOfAccountingFlags();
-
-#ifdef DEBUG
-    PRUint32 mGeneration;
-    void Dump();
-#endif
-
-protected:
-    struct CacheHashKey {
-        void        *mFontOrGroup;
-        const void  *mString;
-        PRUint32     mLength;
-        PRUint32     mAppUnitsPerDevUnit;
-        PRUint32     mStringHash;
-        PRUint64     mUserFontSetGeneration;
-        bool mIsDoubleByteText;
-        bool mIsRTL;
-        bool mEnabledOptionalLigatures;
-        bool mOptimizeSpeed;
-        
-        CacheHashKey(gfxTextRun *aBaseTextRun, void *aFontOrGroup,
-                     PRUint32 aStart, PRUint32 aLength, PRUint32 aHash)
-            : mFontOrGroup(aFontOrGroup), mString(aBaseTextRun->GetTextAt(aStart)),
-              mLength(aLength),
-              mAppUnitsPerDevUnit(aBaseTextRun->GetAppUnitsPerDevUnit()),
-              mStringHash(aHash),
-              mUserFontSetGeneration(aBaseTextRun->GetUserFontSetGeneration()),
-              mIsDoubleByteText((aBaseTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) == 0),
-              mIsRTL(aBaseTextRun->IsRightToLeft()),
-              mEnabledOptionalLigatures((aBaseTextRun->GetFlags() & gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) == 0),
-              mOptimizeSpeed((aBaseTextRun->GetFlags() & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED) != 0)
-        {
-        }
-    };
-
-    class CacheHashEntry : public PLDHashEntryHdr {
-    public:
-        typedef const CacheHashKey &KeyType;
-        typedef const CacheHashKey *KeyTypePointer;
-
-        // When constructing a new entry in the hashtable, the caller of Put()
-        // will fill us in.
-        CacheHashEntry(KeyTypePointer aKey) : mTextRun(nsnull), mWordOffset(0),
-            mHashedByFont(false) { }
-        CacheHashEntry(const CacheHashEntry& toCopy) { NS_ERROR("Should not be called"); }
-        ~CacheHashEntry() { }
-
-        bool KeyEquals(const KeyTypePointer aKey) const;
-        static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
-        static PLDHashNumber HashKey(const KeyTypePointer aKey);
-        enum { ALLOW_MEMMOVE = true };
-
-        gfxTextRun *mTextRun;
-        // The offset of the start of the word in the textrun. The length of
-        // the word is not stored here because we can figure it out by
-        // looking at the textrun's text.
-        PRUint32    mWordOffset:31;
-        // This is set to true when the cache entry was hashed by the first
-        // font in mTextRun's fontgroup; it's false when the cache entry
-        // was hashed by the fontgroup itself.
-        PRUint32    mHashedByFont:1;
-    };
-    
-    // Used to track words that should be copied from one textrun to
-    // another during the textrun construction process
-    struct DeferredWord {
-        gfxTextRun *mSourceTextRun;
-        PRUint32    mSourceOffset;
-        PRUint32    mDestOffset;
-        PRUint32    mLength;
-        PRUint32    mHash;
-    };
-    
-    bool LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
-                      PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
-                      nsTArray<DeferredWord>* aDeferredWords);
-    void FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
-                       const gfxFontGroup::Parameters *aParams,
-                       const nsTArray<DeferredWord>& aDeferredWords,
-                       bool aSuccessful);
-    void RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
-                    PRUint32 aEnd, PRUint32 aHash);
-    void Uninit();
-
-    static size_t MaybeSizeOfEntryExcludingThis(CacheHashEntry *aEntry,
-                                                nsMallocSizeOfFun aMallocSizeOf,
-                                                void *aUserData);
-    static PLDHashOperator ResetSizeOfEntryAccountingFlags(CacheHashEntry *aEntry,
-                                            void *aUserData);
-
-    nsTHashtable<CacheHashEntry> mCache;
-
-    PRInt32 mBidiNumeral;
-
-#ifdef DEBUG
-    static PLDHashOperator CacheDumpEntry(CacheHashEntry* aEntry, void* userArg);
-#endif
-};
-
-NS_IMPL_ISUPPORTS2(TextRunWordCache, nsIObserver, nsISupportsWeakReference)
-
-static TextRunWordCache *gTextRunWordCache = nsnull;
-
-static PRLogModuleInfo *gWordCacheLog = PR_NewLogModule("wordCache");
-
-static const char* kObservedPrefs[] = {
-    "bidi.",
-    "font.",
-    nsnull
-};
-
-void
-TextRunWordCache::Init()
-{
-#ifdef DEBUG
-    mGeneration = 0;
-#endif
-
-    Preferences::AddWeakObservers(this, kObservedPrefs);
-    mBidiNumeral = Preferences::GetInt("bidi.numeral", mBidiNumeral);
-}
-
-void
-TextRunWordCache::Uninit()
-{
-    Preferences::RemoveObservers(this, kObservedPrefs);
-}
-
-NS_IMETHODIMP
-TextRunWordCache::Observe(nsISupports     *aSubject,
-                          const char      *aTopic,
-                          const PRUnichar *aData)
-{
-    if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
-        if (!nsCRT::strcmp(aData, NS_LITERAL_STRING("bidi.numeral").get())) {
-          mBidiNumeral = Preferences::GetInt("bidi.numeral", mBidiNumeral);
-        }
-        mCache.Clear();
-        PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("flushing the textrun cache"));
-#ifdef DEBUG
-        mGeneration++;
-#endif
-    }
-
-    return NS_OK;
-}
-
-static inline PRUint32
-HashMix(PRUint32 aHash, PRUnichar aCh)
-{
-    return (aHash >> 28) ^ (aHash << 4) ^ aCh;
-}
-
-// If the substring of the textrun is rendered entirely in the first font
-// of the textrun's fontgroup, then return that font. Otherwise return the
-// fontgroup.  When a user font set is in use, always return the font group.
-static void *GetWordFontOrGroup(gfxTextRun *aTextRun, PRUint32 aOffset,
-                                PRUint32 aLength)
-{
-    gfxFontGroup *fontGroup = aTextRun->GetFontGroup();
-    if (fontGroup->GetUserFontSet() != nsnull)
-        return fontGroup;
-        
-    PRUint32 glyphRunCount;
-    const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&glyphRunCount);
-    PRUint32 glyphRunIndex = aTextRun->FindFirstGlyphRunContaining(aOffset);
-    gfxFont *firstFont = fontGroup->GetFontAt(0);
-    if (glyphRuns[glyphRunIndex].mFont != firstFont)
-        return fontGroup;
-
-    PRUint32 glyphRunEnd = glyphRunIndex == glyphRunCount - 1
-        ? aTextRun->GetLength() : glyphRuns[glyphRunIndex + 1].mCharacterOffset;
-    if (aOffset + aLength <= glyphRunEnd)
-        return firstFont;
-    return fontGroup;
-}
-
-#define UNICODE_NBSP 0x00A0
-
-// XXX should we treat NBSP or SPACE combined with other characters as a word
-// boundary? Currently this does.
-static bool
-IsBoundarySpace(PRUnichar aChar)
-{
-    return aChar == ' ' || aChar == UNICODE_NBSP;
-}
-
-static bool
-IsWordBoundary(PRUnichar aChar)
-{
-    return IsBoundarySpace(aChar) || gfxFontGroup::IsInvalidChar(aChar);
-}
-
-/**
- * Looks up a word in the cache. If the word is found in the cache
- * (which could be from an existing textrun or an earlier word in the same
- * textrun), we copy the glyphs from the word into the textrun, unless
- * aDeferredWords is non-null (meaning that all words from now on must be
- * copied later instead of now), in which case we add the word to be copied
- * to the list.
- * 
- * If the word is not found in the cache then we add it to the cache with
- * aFirstFont as the key, on the assumption that the word will be matched
- * by aFirstFont. If this assumption is false we fix up the cache later in
- * FinishTextRun. We make this assumption for two reasons:
- * 1) it's usually true so it saves an extra cache lookup if we had to insert
- * the entry later
- * 2) we need to record words that appear in the string in some kind
- * of hash table so we can detect and use them if they appear later in the
- * (in general the string might be huge and contain many repeated words).
- * We might as well use the master hash table for this.
- * 
- * @return true if the word was found in the cache, false otherwise.
- */
-bool
-TextRunWordCache::LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
-                             PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
-                             nsTArray<DeferredWord>* aDeferredWords)
-{
-    if (aEnd <= aStart)
-        return true;
-        
-    gfxFontGroup *fontGroup = aTextRun->GetFontGroup();
-
-    bool useFontGroup = (fontGroup->GetUserFontSet() != nsnull);
-    CacheHashKey key(aTextRun, (useFontGroup ? (void*)fontGroup : (void*)aFirstFont), aStart, aEnd - aStart, aHash);
-    CacheHashEntry *fontEntry = mCache.PutEntry(key);
-    if (!fontEntry)
-        return false;
-    CacheHashEntry *existingEntry = nsnull;
-
-    if (fontEntry->mTextRun) {
-        existingEntry = fontEntry;
-    } else if (useFontGroup) {
-        PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): added using font group", aTextRun, aStart, aEnd - aStart, aHash));
-    } else {
-        // test to see if this can be found using the font group instead
-        PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): added using font", aTextRun, aStart, aEnd - aStart, aHash));
-        key.mFontOrGroup = aTextRun->GetFontGroup();
-        CacheHashEntry *groupEntry = mCache.GetEntry(key);
-        if (groupEntry) {
-            existingEntry = groupEntry;
-            mCache.RawRemoveEntry(fontEntry);
-            PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): removed using font", aTextRun, aStart, aEnd - aStart, aHash));
-            fontEntry = nsnull;
-        }
-    }
-    // At this point, either existingEntry is non-null and points to (surprise!)
-    // an entry for a word in an existing textrun, or fontEntry points
-    // to a cache entry for this word with aFirstFont, which needs to be
-    // filled in or removed.
-
-    if (existingEntry) {
-        if (aDeferredWords) {
-            DeferredWord word = { existingEntry->mTextRun,
-                  existingEntry->mWordOffset, aStart, aEnd - aStart, aHash };
-            aDeferredWords->AppendElement(word);
-        } else {
-            aTextRun->CopyGlyphDataFrom(existingEntry->mTextRun,
-                existingEntry->mWordOffset, aEnd - aStart, aStart);
-        }
-        return true;
-    }
-
-#ifdef DEBUG
-    ++aTextRun->mCachedWords;
-#endif
-    // Set up the cache entry so that if later in this textrun we hit this
-    // entry, we'll copy within our own textrun
-    fontEntry->mTextRun = aTextRun;
-    fontEntry->mWordOffset = aStart;
-    if (!useFontGroup)
-        fontEntry->mHashedByFont = true;
-    return false;
-}
-
-/**
- * Processes all deferred words. Each word is copied from the source
- * textrun to the output textrun. (The source may be an earlier word in the
- * output textrun.) If the word was not matched by the textrun's fontgroup's
- * first font, then we remove the entry we optimistically added to the cache
- * with that font in the key, and add a new entry keyed with the fontgroup
- * instead.
- * 
- * @param aSuccessful if false, then we don't do any word copies and we don't
- * add anything to the cache, but we still remove all the optimistic cache
- * entries.
- */
-void
-TextRunWordCache::FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
-                                const gfxFontGroup::Parameters *aParams,
-                                const nsTArray<DeferredWord>& aDeferredWords,
-                                bool aSuccessful)
-{
-    aTextRun->SetFlagBits(gfxTextRunWordCache::TEXT_IN_CACHE);
-
-    PRUint32 i;
-    gfxFontGroup *fontGroup = aTextRun->GetFontGroup();
-    gfxFont *font = fontGroup->GetFontAt(0);
-    
-    // need to use the font group when user font set is around, since
-    // the first font may change as the result of a font download
-    bool useFontGroup = (fontGroup->GetUserFontSet() != nsnull);
-
-    // copy deferred words from various sources into destination textrun
-    for (i = 0; i < aDeferredWords.Length(); ++i) {
-        const DeferredWord *word = &aDeferredWords[i];
-        gfxTextRun *source = word->mSourceTextRun;
-        if (!source) {
-            source = aNewRun;
-        }
-        // If the word starts inside a cluster we don't want this word
-        // in the cache, so we'll remove the associated cache entry
-        bool wordStartsInsideCluster;
-        bool wordStartsInsideLigature;
-        if (aSuccessful) {
-            wordStartsInsideCluster =
-                !source->IsClusterStart(word->mSourceOffset);
-            wordStartsInsideLigature =
-                !source->IsLigatureGroupStart(word->mSourceOffset);
-        }
-        if (source == aNewRun) {
-            // We created a cache entry for this word based on the assumption
-            // that the word matches GetFontAt(0). If this assumption is false,
-            // we need to remove that cache entry and replace it with an entry
-            // keyed off the fontgroup.
-            bool removeFontKey = !aSuccessful ||
-                wordStartsInsideCluster || wordStartsInsideLigature ||
-                (!useFontGroup && font != GetWordFontOrGroup(aNewRun,
-                                                             word->mSourceOffset,
-                                                             word->mLength));
-            if (removeFontKey) {
-                // We need to remove the current placeholder cache entry
-                CacheHashKey key(aTextRun,
-                                 (useFontGroup ? (void*)fontGroup : (void*)font),
-                                 word->mDestOffset, word->mLength, word->mHash);
-                NS_ASSERTION(mCache.GetEntry(key),
-                             "This entry should have been added previously!");
-                mCache.RemoveEntry(key);
-#ifdef DEBUG
-                --aTextRun->mCachedWords;
-#endif
-                PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): removed using font", aTextRun, word->mDestOffset, word->mLength, word->mHash));
-                
-                if (aSuccessful && !wordStartsInsideCluster && !wordStartsInsideLigature) {
-                    key.mFontOrGroup = fontGroup;
-                    CacheHashEntry *groupEntry = mCache.PutEntry(key);
-                    if (groupEntry) {
-#ifdef DEBUG
-                        ++aTextRun->mCachedWords;
-#endif
-                        PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): added using fontgroup", aTextRun, word->mDestOffset, word->mLength, word->mHash));
-                        groupEntry->mTextRun = aTextRun;
-                        groupEntry->mWordOffset = word->mDestOffset;
-                        groupEntry->mHashedByFont = false;
-                        NS_ASSERTION(mCache.GetEntry(key),
-                                     "We should find the thing we just added!");
-                    }
-                }
-            }
-        }
-        if (aSuccessful) {
-            // Copy the word.
-            PRUint32 sourceOffset = word->mSourceOffset;
-            PRUint32 destOffset = word->mDestOffset;
-            PRUint32 length = word->mLength;
-            nsAutoPtr<gfxTextRun> tmpTextRun;
-            if (wordStartsInsideCluster || wordStartsInsideLigature) {
-                NS_ASSERTION(sourceOffset > 0, "How can the first character be inside a cluster?");
-                if (wordStartsInsideCluster && destOffset > 0 &&
-                    IsBoundarySpace(aTextRun->GetChar(destOffset - 1))) {
-                    // The first character of the word formed a cluster
-                    // with the preceding space.
-                    // We should copy over data for the preceding space
-                    // as well. The glyphs have probably been attached
-                    // to that character.
-                    --sourceOffset;
-                    --destOffset;
-                    ++length;
-                } else {
-                    // URK! This case sucks! We have combining marks or
-                    // part of a ligature at the start of the text. We
-                    // had to prepend a space just so we could detect this
-                    // situation (so we can keep this "word" out of the
-                    // cache). But now the data in aNewRun is no use to us.
-                    // We need to find out what the platform would do
-                    // if the characters were at the start of the text.
-                    if (source->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
-                        tmpTextRun = fontGroup->
-                            MakeTextRun(source->GetText8Bit() + sourceOffset,
-                                        length, aParams, source->GetFlags());
-                    } else {
-                        tmpTextRun = fontGroup->
-                            MakeTextRun(source->GetTextUnicode() + sourceOffset,
-                                        length, aParams, source->GetFlags());
-                    }
-                    if (tmpTextRun) {
-                        source = tmpTextRun;
-                        sourceOffset = 0;
-                    } else {
-                        // If we failed to create the temporary run (OOM),
-                        // skip the word, as if aSuccessful had been FALSE.
-                        // (In practice this is only likely to occur if
-                        // we're on the verge of an OOM crash anyhow.
-                        // But ignoring gfxFontGroup::MakeTextRun() failure
-                        // is bad because it means we'd be using an invalid
-                        // source pointer.)
-                        continue;
-                    }
-                }
-            }
-            aTextRun->CopyGlyphDataFrom(source, sourceOffset, length,
-                                        destOffset);
-            // Fill in additional spaces
-            PRUint32 endCharIndex;
-            if (i + 1 < aDeferredWords.Length()) {
-                endCharIndex = aDeferredWords[i + 1].mDestOffset;
-            } else {
-                endCharIndex = aTextRun->GetLength();
-            }
-            PRUint32 charIndex;
-            for (charIndex = word->mDestOffset + word->mLength;
-                 charIndex < endCharIndex; ++charIndex) {
-                if (IsBoundarySpace(aTextRun->GetChar(charIndex))) {
-                    aTextRun->SetSpaceGlyph(font, aParams->mContext, charIndex);
-                }
-            }
-        }
-    }
-}
-
-static gfxTextRun *
-MakeBlankTextRun(const void* aText, PRUint32 aLength,
-                         gfxFontGroup *aFontGroup,
-                         const gfxFontGroup::Parameters *aParams,
-                         PRUint32 aFlags)
-{
-    nsAutoPtr<gfxTextRun> textRun;
-    textRun = gfxTextRun::Create(aParams, aText, aLength, aFontGroup, aFlags);
-    if (!textRun || !textRun->GetCharacterGlyphs())
-        return nsnull;
-    gfxFont *font = aFontGroup->GetFontAt(0);
-    textRun->AddGlyphRun(font, gfxTextRange::kFontGroup, 0, false);
-#ifdef DEBUG
-    textRun->mCachedWords = 0;
-    textRun->mCacheGeneration = gTextRunWordCache ? gTextRunWordCache->mGeneration : 0;
-#endif
-    return textRun.forget();
-}
-
-gfxTextRun *
-TextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                              gfxFontGroup *aFontGroup,
-                              const gfxFontGroup::Parameters *aParams,
-                              PRUint32 aFlags)
-{
-    // update font list when using user fonts (assures generation is current)
-    aFontGroup->UpdateFontList();
-
-    if (aFontGroup->GetStyle()->size == 0) {
-        // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
-        // them, and always create at least size 1 fonts, i.e. they still
-        // render something for size 0 fonts.
-        return MakeBlankTextRun(aText, aLength, aFontGroup, aParams, aFlags);
-    }
-
-    nsAutoPtr<gfxTextRun> textRun;
-    textRun = gfxTextRun::Create(aParams, aText, aLength, aFontGroup, aFlags);
-    if (!textRun || !textRun->GetCharacterGlyphs())
-        return nsnull;
-#ifdef DEBUG
-    textRun->mCachedWords = 0;
-    textRun->mCacheGeneration = mGeneration;
-#endif
-
-    gfxFont *font = aFontGroup->GetFontAt(0);
-    nsresult rv =
-        textRun->AddGlyphRun(font, gfxTextRange::kFontGroup, 0, false);
-    NS_ENSURE_SUCCESS(rv, nsnull);
-
-    nsAutoTArray<PRUnichar,200> tempString;
-    nsAutoTArray<DeferredWord,50> deferredWords;
-    nsAutoTArray<nsAutoPtr<gfxTextRun>,10> transientRuns;
-    PRUint32 i;
-    PRUint32 wordStart = 0;
-    PRUint32 hash = 0;
-    bool seenDigitToModify = false;
-    bool needsNumeralProcessing =
-        mBidiNumeral != IBMBIDI_NUMERAL_NOMINAL;
-    for (i = 0; i <= aLength; ++i) {
-        PRUnichar ch = i < aLength ? aText[i] : ' ';
-        if (!seenDigitToModify && needsNumeralProcessing) {
-            // check if there is a digit that needs to be transformed
-            if (HandleNumberInChar(ch, !!(i > 0 ?
-                                       IS_ARABIC_CHAR(aText[i-1]) :
-                                       (aFlags & gfxTextRunWordCache::TEXT_INCOMING_ARABICCHAR)),
-                                   mBidiNumeral) != ch)
-                seenDigitToModify = true;
-        }
-        if (IsWordBoundary(ch)) {
-            if (seenDigitToModify) {
-                // the word included at least one digit that is modified by the current
-                // bidi.numerals setting, so we must not use the cache for this word;
-                // instead, we'll create a new textRun and a DeferredWord entry pointing to it
-                PRUint32 length = i - wordStart;
-                nsAutoArrayPtr<PRUnichar> numString;
-                numString = new PRUnichar[length];
-                for (PRUint32 j = 0; j < length; ++j) {
-                    numString[j] = HandleNumberInChar(aText[wordStart+j],
-                                                      !!(j > 0 ?
-                                                          IS_ARABIC_CHAR(numString[j-1]) :
-                                                          (aFlags & gfxTextRunWordCache::TEXT_INCOMING_ARABICCHAR)),
-                                                      mBidiNumeral);
-                }
-                // now we make a transient textRun for the transformed word; this will not be cached
-                gfxTextRun *numRun;
-                numRun =
-                    aFontGroup->MakeTextRun(numString.get(), length, aParams,
-                                            aFlags & ~(gfxTextRunFactory::TEXT_IS_PERSISTENT |
-                                                       gfxTextRunFactory::TEXT_IS_8BIT));
-                // If MakeTextRun failed, numRun will be null, which is bad...
-                // we'll just pretend there wasn't a digit to process.
-                // This means we won't have the correct numerals, but at least
-                // we're not trying to copy glyph data from an invalid source.
-                // In practice it's unlikely to happen unless we're very close
-                // to crashing due to OOM.
-                if (numRun) {
-                    DeferredWord word = { numRun, 0, wordStart, length, hash };
-                    deferredWords.AppendElement(word);
-                    transientRuns.AppendElement(numRun);
-                } else {
-                    seenDigitToModify = false;
-                }
-            }
-
-            if (!seenDigitToModify) {
-                // didn't need to modify digits (or failed to do so)
-                bool hit = LookupWord(textRun, font, wordStart, i, hash,
-                                        deferredWords.Length() == 0 ? nsnull : &deferredWords);
-                if (!hit) {
-                    // Always put a space before the word so we can detect
-                    // combining characters at the start of a word
-                    tempString.AppendElement(' ');
-                    PRUint32 offset = tempString.Length();
-                    PRUint32 length = i - wordStart;
-                    PRUnichar *chars = tempString.AppendElements(length);
-                    if (!chars) {
-                        FinishTextRun(textRun, nsnull, nsnull, deferredWords, false);
-                        return nsnull;
-                    }
-                    memcpy(chars, aText + wordStart, length*sizeof(PRUnichar));
-                    DeferredWord word = { nsnull, offset, wordStart, length, hash };
-                    deferredWords.AppendElement(word);
-                }
-
-                if (deferredWords.Length() == 0) {
-                    if (IsBoundarySpace(ch) && i < aLength) {
-                        textRun->SetSpaceGlyph(font, aParams->mContext, i);
-                    } // else we should set this character to be invisible missing,
-                      // but it already is because the textrun is blank!
-                }
-            } else {
-                seenDigitToModify = false;
-            }
-
-            hash = 0;
-            wordStart = i + 1;
-        } else {
-            hash = HashMix(hash, ch);
-        }
-    }
-
-    if (deferredWords.Length() == 0) {
-        // We got everything from the cache, so we're done. No point in calling
-        // FinishTextRun.
-        // This textrun is not referenced by the cache.
-        return textRun.forget();
-    }
-
-    // create textrun for unknown words
-    gfxTextRunFactory::Parameters params =
-        { aParams->mContext, nsnull, nsnull, nsnull, 0, aParams->mAppUnitsPerDevUnit };
-    nsAutoPtr<gfxTextRun> newRun;
-    if (tempString.Length() == 0) {
-        newRun = aFontGroup->MakeEmptyTextRun(&params, aFlags);
-    } else {
-        newRun = aFontGroup->MakeTextRun(tempString.Elements(), tempString.Length(),
-                                         &params, aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
-    }
-    FinishTextRun(textRun, newRun, aParams, deferredWords, newRun != nsnull);
-    return textRun.forget();
-}
-
-gfxTextRun *
-TextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
-                              gfxFontGroup *aFontGroup,
-                              const gfxFontGroup::Parameters *aParams,
-                              PRUint32 aFlags)
-{
-    // update font list when using user fonts (assures generation is current)
-    aFontGroup->UpdateFontList();
-
-    if (aFontGroup->GetStyle()->size == 0) {
-        // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
-        // them, and always create at least size 1 fonts, i.e. they still
-        // render something for size 0 fonts.
-        return MakeBlankTextRun(aText, aLength, aFontGroup, aParams, aFlags);
-    }
-
-    aFlags |= gfxTextRunFactory::TEXT_IS_8BIT;
-    nsAutoPtr<gfxTextRun> textRun;
-    textRun = gfxTextRun::Create(aParams, aText, aLength, aFontGroup, aFlags);
-    if (!textRun || !textRun->GetCharacterGlyphs())
-        return nsnull;
-#ifdef DEBUG
-    textRun->mCachedWords = 0;
-    textRun->mCacheGeneration = mGeneration;
-#endif
-
-    gfxFont *font = aFontGroup->GetFontAt(0);
-    nsresult rv =
-        textRun->AddGlyphRun(font, gfxTextRange::kFontGroup, 0, false);
-    NS_ENSURE_SUCCESS(rv, nsnull);
-
-    nsAutoTArray<PRUint8,200> tempString;
-    nsAutoTArray<DeferredWord,50> deferredWords;
-    nsAutoTArray<nsAutoPtr<gfxTextRun>,10> transientRuns;
-    PRUint32 i;
-    PRUint32 wordStart = 0;
-    PRUint32 hash = 0;
-    bool seenDigitToModify = false;
-    bool needsNumeralProcessing =
-        mBidiNumeral != IBMBIDI_NUMERAL_NOMINAL;
-    for (i = 0; i <= aLength; ++i) {
-        PRUint8 ch = i < aLength ? aText[i] : ' ';
-        if (!seenDigitToModify && needsNumeralProcessing) {
-            // check if there is a digit that needs to be transformed
-            if (HandleNumberInChar(ch, i == 0 && (aFlags & gfxTextRunWordCache::TEXT_INCOMING_ARABICCHAR),
-                                   mBidiNumeral) != ch)
-                seenDigitToModify = true;
-        }
-        if (IsWordBoundary(ch)) {
-            if (seenDigitToModify) {
-                // see parallel code in the 16-bit method above
-                PRUint32 length = i - wordStart;
-                nsAutoArrayPtr<PRUnichar> numString;
-                numString = new PRUnichar[length];
-                for (PRUint32 j = 0; j < length; ++j) {
-                    numString[j] = HandleNumberInChar(aText[wordStart+j],
-                                                      !!(j > 0 ?
-                                                          IS_ARABIC_CHAR(numString[j-1]) :
-                                                          (aFlags & gfxTextRunWordCache::TEXT_INCOMING_ARABICCHAR)),
-                                                      mBidiNumeral);
-                }
-                // now we make a transient textRun for the transformed word; this will not be cached
-                gfxTextRun *numRun;
-                numRun =
-                    aFontGroup->MakeTextRun(numString.get(), length, aParams,
-                                            aFlags & ~(gfxTextRunFactory::TEXT_IS_PERSISTENT |
-                                                       gfxTextRunFactory::TEXT_IS_8BIT));
-                if (numRun) {
-                    DeferredWord word = { numRun, 0, wordStart, length, hash };
-                    deferredWords.AppendElement(word);
-                    transientRuns.AppendElement(numRun);
-                } else {
-                    seenDigitToModify = false;
-                }
-            }
-
-            if (!seenDigitToModify) {
-                bool hit = LookupWord(textRun, font, wordStart, i, hash,
-                                        deferredWords.Length() == 0 ? nsnull : &deferredWords);
-                if (!hit) {
-                    if (tempString.Length() > 0) {
-                        tempString.AppendElement(' ');
-                    }
-                    PRUint32 offset = tempString.Length();
-                    PRUint32 length = i - wordStart;
-                    PRUint8 *chars = tempString.AppendElements(length);
-                    if (!chars) {
-                        FinishTextRun(textRun, nsnull, nsnull, deferredWords, false);
-                        return nsnull;
-                    }
-                    memcpy(chars, aText + wordStart, length*sizeof(PRUint8));
-                    DeferredWord word = { nsnull, offset, wordStart, length, hash };
-                    deferredWords.AppendElement(word);
-                }
-
-                if (deferredWords.Length() == 0) {
-                    if (IsBoundarySpace(ch) && i < aLength) {
-                        textRun->SetSpaceGlyph(font, aParams->mContext, i);
-                    } // else we should set this character to be invisible missing,
-                      // but it already is because the textrun is blank!
-                }
-            } else {
-                seenDigitToModify = false;
-            }
-
-            hash = 0;
-            wordStart = i + 1;
-        } else {
-            hash = HashMix(hash, ch);
-        }
-    }
-
-    if (deferredWords.Length() == 0) {
-        // We got everything from the cache, so we're done. No point in calling
-        // FinishTextRun.
-        // This textrun is not referenced by the cache.
-        return textRun.forget();
-    }
-
-    // create textrun for unknown words
-    gfxTextRunFactory::Parameters params =
-        { aParams->mContext, nsnull, nsnull, nsnull, 0, aParams->mAppUnitsPerDevUnit };
-    nsAutoPtr<gfxTextRun> newRun;
-    if (tempString.Length() == 0) {
-        newRun = aFontGroup->MakeEmptyTextRun(&params, aFlags);
-    } else {
-        newRun = aFontGroup->MakeTextRun(tempString.Elements(), tempString.Length(),
-                                         &params, aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
-    }
-    FinishTextRun(textRun, newRun, aParams, deferredWords, newRun != nsnull);
-    return textRun.forget();
-}
-
-void
-TextRunWordCache::RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
-                             PRUint32 aEnd, PRUint32 aHash)
-{
-    if (aEnd <= aStart)
-        return;
-
-    PRUint32 length = aEnd - aStart;
-    CacheHashKey key(aTextRun, GetWordFontOrGroup(aTextRun, aStart, length),
-                     aStart, length, aHash);
-    CacheHashEntry *entry = mCache.GetEntry(key);
-    if (entry && entry->mTextRun == aTextRun) {
-        // XXX would like to use RawRemoveEntry here plus some extra method
-        // that conditionally shrinks the hashtable
-        mCache.RemoveEntry(key);
-#ifdef DEBUG
-        --aTextRun->mCachedWords;
-#endif
-        PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): removed using %s",
-            aTextRun, aStart, length, aHash,
-            key.mFontOrGroup == aTextRun->GetFontGroup() ? "fontgroup" : "font"));
-    }
-}
-
-// Remove a textrun from the cache by looking up each word and removing it
-void
-TextRunWordCache::RemoveTextRun(gfxTextRun *aTextRun)
-{
-#ifdef DEBUG
-    if (aTextRun->mCacheGeneration != mGeneration) {
-        PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("cache generation changed (aTextRun %p)", aTextRun));
-        return;
-    }
-#endif
-    PRUint32 i;
-    PRUint32 wordStart = 0;
-    PRUint32 hash = 0;
-    for (i = 0; i < aTextRun->GetLength(); ++i) {
-        PRUnichar ch = aTextRun->GetChar(i);
-        if (IsWordBoundary(ch)) {
-            RemoveWord(aTextRun, wordStart, i, hash);
-            hash = 0;
-            wordStart = i + 1;
-        } else {
-            hash = HashMix(hash, ch);
-        }
-    }
-    RemoveWord(aTextRun, wordStart, i, hash);
-#ifdef DEBUG
-    NS_ASSERTION(aTextRun->mCachedWords == 0,
-                 "Textrun was not completely removed from the cache!");
-#endif
-}
-
-/*static*/ size_t
-TextRunWordCache::MaybeSizeOfEntryExcludingThis(CacheHashEntry *aEntry,
-                                                nsMallocSizeOfFun aMallocSizeOf,
-                                                void *)
-{
-    gfxTextRun *run = aEntry->mTextRun;
-    if (run) {
-        return run->MaybeSizeOfIncludingThis(aMallocSizeOf);
-    }
-    return 0;
-}
-
-/*static*/ PLDHashOperator
-TextRunWordCache::ResetSizeOfEntryAccountingFlags(CacheHashEntry *aEntry, void *)
-{
-    gfxTextRun *run = aEntry->mTextRun;
-    if (run) {
-        run->ResetSizeOfAccountingFlags();
-    }
-    return PL_DHASH_NEXT;
-}
-
-size_t
-TextRunWordCache::MaybeSizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf)
-{
-    return mCache.SizeOfExcludingThis(MaybeSizeOfEntryExcludingThis, aMallocSizeOf);
-}
-
-void
-TextRunWordCache::ResetSizeOfAccountingFlags()
-{
-    mCache.EnumerateEntries(ResetSizeOfEntryAccountingFlags, nsnull);
-}
-
-static bool
-CompareDifferentWidthStrings(const PRUint8 *aStr1, const PRUnichar *aStr2,
-                             PRUint32 aLength)
-{
-    PRUint32 i;
-    for (i = 0; i < aLength; ++i) {
-        if (aStr1[i] != aStr2[i])
-            return false;
-    }
-    return true;
-}
-
-static bool
-IsWordEnd(gfxTextRun *aTextRun, PRUint32 aOffset)
-{
-    PRUint32 runLength = aTextRun->GetLength();
-    if (aOffset == runLength)
-        return true;
-    if (aOffset > runLength)
-        return false;
-    return IsWordBoundary(aTextRun->GetChar(aOffset));
-}
-
-static void *
-GetFontOrGroup(gfxFontGroup *aFontGroup, bool aUseFont)
-{
-    return aUseFont
-        ? static_cast<void *>(aFontGroup->GetFontAt(0))
-        : static_cast<void *>(aFontGroup);
-}
-
-bool
-TextRunWordCache::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
-{
-    if (!mTextRun)
-        return false;
-
-    PRUint32 length = aKey->mLength;
-    gfxFontGroup *fontGroup = mTextRun->GetFontGroup();
-    if (!IsWordEnd(mTextRun, mWordOffset + length) ||
-        GetFontOrGroup(fontGroup, mHashedByFont) != aKey->mFontOrGroup ||
-        aKey->mAppUnitsPerDevUnit != mTextRun->GetAppUnitsPerDevUnit() ||
-        aKey->mIsRTL != mTextRun->IsRightToLeft() ||
-        aKey->mEnabledOptionalLigatures != ((mTextRun->GetFlags() & gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) == 0) ||
-        aKey->mOptimizeSpeed != ((mTextRun->GetFlags() & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED) != 0) ||
-        aKey->mUserFontSetGeneration != (mTextRun->GetUserFontSetGeneration()))
-        return false;
-
-    if (mTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
-        const PRUint8 *text = mTextRun->GetText8Bit() + mWordOffset;
-        if (!aKey->mIsDoubleByteText)
-            return memcmp(text, aKey->mString, length) == 0;
-        return CompareDifferentWidthStrings(text,
-                                            static_cast<const PRUnichar *>(aKey->mString), length);
-    } else {
-        const PRUnichar *text = mTextRun->GetTextUnicode() + mWordOffset;
-        if (aKey->mIsDoubleByteText)
-            return memcmp(text, aKey->mString, length*sizeof(PRUnichar)) == 0;
-        return CompareDifferentWidthStrings(static_cast<const PRUint8 *>(aKey->mString),
-                                            text, length);
-    }
-}
-
-PLDHashNumber
-TextRunWordCache::CacheHashEntry::HashKey(const KeyTypePointer aKey)
-{
-    // only use lower 32 bits of generation counter in hash key, 
-    // since these vary the most
-    PRUint32 fontSetGen;
-    LL_L2UI(fontSetGen, aKey->mUserFontSetGeneration);
-
-    return aKey->mStringHash + fontSetGen + (PRUint32)(intptr_t)aKey->mFontOrGroup + aKey->mAppUnitsPerDevUnit +
-        aKey->mIsDoubleByteText + aKey->mIsRTL*2 + aKey->mEnabledOptionalLigatures*4 +
-        aKey->mOptimizeSpeed*8;
-}
-
-#ifdef DEBUG
-PLDHashOperator
-TextRunWordCache::CacheDumpEntry(CacheHashEntry* aEntry, void* userArg)
-{
-    FILE* output = static_cast<FILE*>(userArg);
-    if (!aEntry->mTextRun) {
-        fprintf(output, "<EMPTY>\n");
-        return PL_DHASH_NEXT;
-    }
-    fprintf(output, "Word at %p:%d => ", static_cast<void*>(aEntry->mTextRun), aEntry->mWordOffset);
-    aEntry->mTextRun->Dump(output);
-    fprintf(output, " (hashed by %s)\n", aEntry->mHashedByFont ? "font" : "fontgroup");
-    return PL_DHASH_NEXT;
-}
-
-void
-TextRunWordCache::Dump()
-{
-    mCache.EnumerateEntries(CacheDumpEntry, stdout);
-}
-#endif
-
-nsresult
-gfxTextRunWordCache::Init()
-{
-    gTextRunWordCache = new TextRunWordCache();
-    if (gTextRunWordCache) {
-        // ensure there is a reference before the AddObserver calls;
-        // this will be Release()'d in gfxTextRunWordCache::Shutdown()
-        NS_ADDREF(gTextRunWordCache);
-        gTextRunWordCache->Init();
-    }
-    return gTextRunWordCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-}
-
-void
-gfxTextRunWordCache::Shutdown()
-{
-    NS_IF_RELEASE(gTextRunWordCache);
-}
-
-gfxTextRun *
-gfxTextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                                 gfxFontGroup *aFontGroup,
-                                 const gfxFontGroup::Parameters *aParams,
-                                 PRUint32 aFlags)
-{
-    if (!gTextRunWordCache)
-        return nsnull;
-    return gTextRunWordCache->MakeTextRun(aText, aLength, aFontGroup, aParams, aFlags);
-}
-
-gfxTextRun *
-gfxTextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
-                                 gfxFontGroup *aFontGroup,
-                                 const gfxFontGroup::Parameters *aParams,
-                                 PRUint32 aFlags)
-{
-    if (!gTextRunWordCache)
-        return nsnull;
-    return gTextRunWordCache->MakeTextRun(aText, aLength, aFontGroup, aParams, aFlags);
-}
-
-void
-gfxTextRunWordCache::RemoveTextRun(gfxTextRun *aTextRun)
-{
-    if (!gTextRunWordCache)
-        return;
-    gTextRunWordCache->RemoveTextRun(aTextRun);
-}
-
-void
-gfxTextRunWordCache::Flush()
-{
-    if (!gTextRunWordCache)
-        return;
-    gTextRunWordCache->Flush();
-}
-
-size_t
-gfxTextRunWordCache::MaybeSizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf)
-{
-    if (!gTextRunWordCache) {
-        return 0;
-    }
-    return gTextRunWordCache->MaybeSizeOfExcludingThis(aMallocSizeOf);
-}
-
-void
-gfxTextRunWordCache::ResetSizeOfAccountingFlags()
-{
-    if (gTextRunWordCache) {
-        gTextRunWordCache->ResetSizeOfAccountingFlags();
-    }
-}
-
deleted file mode 100644
--- a/gfx/thebes/gfxTextRunWordCache.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Foundation code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2006
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Vladimir Vukicevic <vladimir@pobox.com>
- *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
- *   Jonathan Kew <jfkthame@gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#ifndef GFX_TEXT_RUN_WORD_CACHE_H
-#define GFX_TEXT_RUN_WORD_CACHE_H
-
-#include "gfxFont.h"
-
-/**
- * Cache individual "words" (strings delimited by white-space or white-space-like
- * characters that don't involve kerning or ligatures) in textruns.
-  */
-class THEBES_API gfxTextRunWordCache {
-public:
-    enum {
-      TEXT_IN_CACHE = 0x10000000,
-
-      /**
-       * nsTextFrameThebes sets these, but they're defined here rather than in
-       * nsTextFrameUtils.h because the cache also needs to check the _INCOMING flag
-       */
-      TEXT_TRAILING_ARABICCHAR = 0x20000000,
-      /**
-       * When set, the previous character for this textrun was an Arabic
-       * character.  This is used for the context detection necessary for
-       * bidi.numeral implementation.
-       */
-      TEXT_INCOMING_ARABICCHAR = 0x40000000,
-
-      TEXT_UNUSED_FLAGS = 0x80000000
-    };
-
-    /**
-     * Create a textrun using cached words.
-     * Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
-     * treated as invisible missing.
-     * @param aFlags the flag TEXT_IS_ASCII must be set by the caller,
-     * if applicable; TEXT_IN_CACHE is added if we have a reference to
-     * the textrun in the cache and RemoveTextRun must be called when the
-     * textrun dies.
-     */
-    static gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                                   gfxFontGroup *aFontGroup,
-                                   const gfxFontGroup::Parameters *aParams,
-                                   PRUint32 aFlags);
-    /**
-     * Create a textrun using cached words.
-     * Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
-     * treated as invisible missing.
-     * @param aFlags the flag TEXT_IS_ASCII must be set by the caller,
-     * if applicable; TEXT_IN_CACHE is added if we have a reference to
-     * the textrun in the cache and RemoveTextRun must be called when the
-     * textrun dies.
-     */
-    static gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
-                                   gfxFontGroup *aFontGroup,
-                                   const gfxFontGroup::Parameters *aParams,
-                                   PRUint32 aFlags);
-
-    /**
-     * Remove a textrun from the cache. This must be called before aTextRun
-     * is deleted! The text in the textrun must still be valid.
-     */
-    static void RemoveTextRun(gfxTextRun *aTextRun);
-
-    /**
-     * Flush the textrun cache. This must be called if a configuration
-     * change that would affect textruns is applied.
-     */
-    static void Flush();
-
-    /**
-     * This adds the storage used for each textRun to the total, and sets the
-     * TEXT_RUN_MEMORY_ACCOUNTED flag to avoid double- accounting. (Runs with
-     * this flag already set will be skipped.)
-     */
-    static size_t MaybeSizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf);
-
-    /**
-     * This clears the TEXT_RUN_MEMORY_ACCOUNTED flag on each textRun found.
-     */
-    static void ResetSizeOfAccountingFlags();
-
-protected:
-    friend class gfxPlatform;
-
-    static nsresult Init();
-    static void Shutdown();
-};
-
-#endif /* GFX_TEXT_RUN_WORD_CACHE_H */
--- a/gfx/thebes/gfxUniscribeShaper.cpp
+++ b/gfx/thebes/gfxUniscribeShaper.cpp
@@ -249,33 +249,33 @@ public:
                                      sfp);
         if (rv == E_PENDING) {
             SelectFont();
             rv = ScriptGetFontProperties(mDC, mShaper->ScriptCache(),
                                          sfp);
         }
     }
 
-    void SaveGlyphs(gfxTextRun *aRun, PRUint32 aRunStart) {
-        PRUint32 offsetInRun = aRunStart + mScriptItem->iCharPos;
+    void SaveGlyphs(gfxShapedWord *aShapedWord) {
+        PRUint32 offsetInRun = mScriptItem->iCharPos;
 
         // XXX We should store this in the item and only fetch it once
         SCRIPT_FONTPROPERTIES sfp;
         ScriptFontProperties(&sfp);
 
         PRUint32 offset = 0;
-        nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs;
-        gfxTextRun::CompressedGlyph g;
-        const PRUint32 appUnitsPerDevUnit = aRun->GetAppUnitsPerDevUnit();
+        nsAutoTArray<gfxShapedWord::DetailedGlyph,1> detailedGlyphs;
+        gfxShapedWord::CompressedGlyph g;
+        const PRUint32 appUnitsPerDevUnit = aShapedWord->AppUnitsPerDevUnit();
         while (offset < mItemLength) {
             PRUint32 runOffset = offsetInRun + offset;
-            bool atClusterStart = aRun->IsClusterStart(runOffset);
+            bool atClusterStart = aShapedWord->IsClusterStart(runOffset);
             if (offset > 0 && mClusters[offset] == mClusters[offset - 1]) {
                 g.SetComplex(atClusterStart, false, 0);
-                aRun->SetGlyphs(runOffset, g, nsnull);
+                aShapedWord->SetGlyphs(runOffset, g, nsnull);
             } else {
                 // Count glyphs for this character
                 PRUint32 k = mClusters[offset];
                 PRUint32 glyphCount = mNumGlyphs - k;
                 PRUint32 nextClusterOffset;
                 bool missing = IsGlyphMissing(&sfp, k);
                 for (nextClusterOffset = offset + 1; nextClusterOffset < mItemLength; ++nextClusterOffset) {
                     if (mClusters[nextClusterOffset] > k) {
@@ -292,46 +292,49 @@ public:
                 PRInt32 advance = mAdvances[k]*appUnitsPerDevUnit;
                 WORD glyph = mGlyphs[k];
                 NS_ASSERTION(!gfxFontGroup::IsInvalidChar(mItemString[offset]),
                              "invalid character detected");
                 if (missing) {
                     if (NS_IS_HIGH_SURROGATE(mItemString[offset]) &&
                         offset + 1 < mItemLength &&
                         NS_IS_LOW_SURROGATE(mItemString[offset + 1])) {
-                        aRun->SetMissingGlyph(runOffset,
-                                              SURROGATE_TO_UCS4(mItemString[offset],
-                                                                mItemString[offset + 1]));
+                        aShapedWord->SetMissingGlyph(runOffset,
+                                                     SURROGATE_TO_UCS4(mItemString[offset],
+                                                                       mItemString[offset + 1]),
+                                                     mShaper->GetFont());
                     } else {
-                        aRun->SetMissingGlyph(runOffset, mItemString[offset]);
+                        aShapedWord->SetMissingGlyph(runOffset, mItemString[offset],
+                                                     mShaper->GetFont());
                     }
                 } else if (glyphCount == 1 && advance >= 0 &&
                     mOffsets[k].dv == 0 && mOffsets[k].du == 0 &&
-                    gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
-                    gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph) &&
+                    gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advance) &&
+                    gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(glyph) &&
                     atClusterStart)
                 {
-                    aRun->SetSimpleGlyph(runOffset, g.SetSimpleGlyph(advance, glyph));
+                    aShapedWord->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->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;
+                        details->mAdvance = mAdvances[k + i] * appUnitsPerDevUnit;
+                        details->mXOffset = float(mOffsets[k + i].du) * appUnitsPerDevUnit *
+                            aShapedWord->GetDirection();
+                        details->mYOffset = - float(mOffsets[k + i].dv) * appUnitsPerDevUnit;
                     }
-                    aRun->SetGlyphs(runOffset,
-                                    g.SetComplex(atClusterStart, true,
-                                                 glyphCount),
-                                    detailedGlyphs.Elements());
+                    aShapedWord->SetGlyphs(runOffset,
+                                           g.SetComplex(atClusterStart, true,
+