[Cocoa] Glyph lookup should be language-sensitive (specifically between Yiddish and Hebrew)
https://bugs.webkit.org/show_bug.cgi?id=77568
<rdar://problem/14649193>

Reviewed by Simon Fraser.

Source/WebCore:

Switch from CTFontTransformGlyphs() to CTFontTransformGlyphsWithLanguage().

CTFontTransformGlyphsWithLanguage() accepts a callback when it needs to insert
glyphs and the glyph buffer isn't big enough. This patch hooks up this callback to
a "makeHole()" function which reallocs the glyph buffer so the hole can be filled in
by shaping.

We cache the CFDictionaries of the CFAttributedString we pass into CoreText using a
HashMap stored in Font.

Tests: fast/text/locale-shaping-complex.html
       fast/text/locale-shaping.html

* platform/graphics/Font.cpp:
(WebCore::Font::applyTransforms const):
* platform/graphics/Font.h:
(WebCore::Font::CFStringAttributesKey::CFStringAttributesKey):
(WebCore::Font::CFStringAttributesKey::operator== const):
(WebCore::Font::CFStringAttributesKey::operator!= const):
(WebCore::Font::CFStringAttributesKey::isHashTableDeletedValue const):
(WebCore::Font::CFStringAttributesKey::computeHash const):
(WebCore::Font::CFStringAttributesKeyHash::hash):
(WebCore::Font::CFStringAttributesKeyHash::equal):
* platform/graphics/FontCascade.cpp:
(WebCore::FontCascade::widthForSimpleText const):
* platform/graphics/GlyphBuffer.h:
(WebCore::GlyphBuffer::isEmpty const):
(WebCore::GlyphBuffer::size const):
(WebCore::GlyphBuffer::clear):
(WebCore::GlyphBuffer::advances const):
(WebCore::GlyphBuffer::fontAt const):
(WebCore::GlyphBuffer::add):
(WebCore::GlyphBuffer::remove):
(WebCore::GlyphBuffer::makeHole):
(WebCore::GlyphBuffer::shrink):
(WebCore::GlyphBuffer::swap):
(WebCore::GlyphBuffer::advancesCount const): Deleted.
* platform/graphics/WidthIterator.cpp:
(WebCore::WidthIterator::applyFontTransforms):
* platform/graphics/WidthIterator.h:
* platform/graphics/cocoa/FontCocoa.mm:
(WebCore::Font::applyTransforms const):
* platform/graphics/mac/ComplexTextControllerCoreText.mm:
(WebCore::ComplexTextController::collectComplexTextRunsForCharacters):
* platform/graphics/mac/SimpleFontDataCoreText.cpp:
(WebCore::Font::getCFStringAttributes const):

Source/WebCore/PAL:

* pal/spi/cocoa/CoreTextSPI.h:

Source/WTF:

* wtf/Platform.h:

LayoutTests:

* fast/text/locale-shaping-complex-expected-mismatch.html: Added.
* fast/text/locale-shaping-complex.html: Added.
* fast/text/locale-shaping-expected-mismatch.html: Added.
* fast/text/locale-shaping.html: Added.
* platform/gtk/TestExpectations:
* platform/mac/TestExpectations:
* platform/win/TestExpectations:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@254534 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 4931841..dd926cc 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,19 @@
+2020-01-14  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        [Cocoa] Glyph lookup should be language-sensitive (specifically between Yiddish and Hebrew)
+        https://bugs.webkit.org/show_bug.cgi?id=77568
+        <rdar://problem/14649193>
+
+        Reviewed by Simon Fraser.
+
+        * fast/text/locale-shaping-complex-expected-mismatch.html: Added.
+        * fast/text/locale-shaping-complex.html: Added.
+        * fast/text/locale-shaping-expected-mismatch.html: Added.
+        * fast/text/locale-shaping.html: Added.
+        * platform/gtk/TestExpectations:
+        * platform/mac/TestExpectations:
+        * platform/win/TestExpectations:
+
 2020-01-14  Lauro Moura  <lmoura@igalia.com>
 
         Gardening after r251750
diff --git a/LayoutTests/fast/text/locale-shaping-complex-expected-mismatch.html b/LayoutTests/fast/text/locale-shaping-complex-expected-mismatch.html
new file mode 100644
index 0000000..63e0b3e
--- /dev/null
+++ b/LayoutTests/fast/text/locale-shaping-complex-expected-mismatch.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div lang="arab" style="font: 100px 'Comic Sans MS';">&#xED;&#xED;A&#x300;</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/locale-shaping-complex.html b/LayoutTests/fast/text/locale-shaping-complex.html
new file mode 100644
index 0000000..d6c9365
--- /dev/null
+++ b/LayoutTests/fast/text/locale-shaping-complex.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div lang="lt" style="font: 100px 'Comic Sans MS';">&#xED;&#xED;A&#x300;</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/locale-shaping-expected-mismatch.html b/LayoutTests/fast/text/locale-shaping-expected-mismatch.html
new file mode 100644
index 0000000..6be6abf
--- /dev/null
+++ b/LayoutTests/fast/text/locale-shaping-expected-mismatch.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div lang="arab" style="font: 100px 'Comic Sans MS';">&#xED;&#xED;</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/locale-shaping.html b/LayoutTests/fast/text/locale-shaping.html
new file mode 100644
index 0000000..a520582
--- /dev/null
+++ b/LayoutTests/fast/text/locale-shaping.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div lang="lt" style="font: 100px 'Comic Sans MS';">&#xED;&#xED;</div>
+</body>
+</html>
diff --git a/LayoutTests/platform/gtk/TestExpectations b/LayoutTests/platform/gtk/TestExpectations
index 4adc734..e446e32 100644
--- a/LayoutTests/platform/gtk/TestExpectations
+++ b/LayoutTests/platform/gtk/TestExpectations
@@ -1206,6 +1206,10 @@
 
 webkit.org/b/206223 imported/mozilla/svg/svg-integration/clipPath-html-02.xhtml
 
+# locale-specific shaping isn't implemented on any non-Cocoa port yet.
+webkit.org/b/77568 fast/text/locale-shaping.html [ ImageOnlyFailure ]
+webkit.org/b/77568 fast/text/locale-shaping-complex.html [ ImageOnlyFailure ]
+
 #////////////////////////////////////////////////////////////////////////////////////////
 # End of Expected failures.
 #
diff --git a/LayoutTests/platform/mac/TestExpectations b/LayoutTests/platform/mac/TestExpectations
index 7cf660d..c244231 100644
--- a/LayoutTests/platform/mac/TestExpectations
+++ b/LayoutTests/platform/mac/TestExpectations
@@ -1928,4 +1928,8 @@
 # The navigator.language tests rely on functionality only available in recent releases of macOS Catalina and onward.
 webkit.org/b/200043 [ Sierra HighSierra Mojave Catalina ] fast/text/international/system-language/navigator-language [ Pass Failure ]
 
-webkit.org/b/204312 imported/w3c/web-platform-tests/svg/import/struct-dom-06-b-manual.svg [ Failure Pass ]
\ No newline at end of file
+webkit.org/b/204312 imported/w3c/web-platform-tests/svg/import/struct-dom-06-b-manual.svg [ Failure Pass ]
+
+# Locale-specific shaping is only enabled on certain OSes.
+webkit.org/b/77568 [ Sierra HighSierra Mojave ] fast/text/locale-shaping.html [ ImageOnlyFailure ]
+webkit.org/b/77568 [ Sierra HighSierra Mojave ] fast/text/locale-shaping-complex.html [ ImageOnlyFailure ]
diff --git a/LayoutTests/platform/win/TestExpectations b/LayoutTests/platform/win/TestExpectations
index 1e71373..50f247d 100644
--- a/LayoutTests/platform/win/TestExpectations
+++ b/LayoutTests/platform/win/TestExpectations
@@ -689,6 +689,10 @@
 fast/visual-viewport/client-coordinates-relative-to-layout-viewport.html [ Failure ]
 fast/visual-viewport/zoomed-fixed-scroll-down-then-up.html [ Failure ]
 
+# locale-specific shaping isn't implemented on any non-Cocoa port yet.
+webkit.org/b/77568 fast/text/locale-shaping.html [ ImageOnlyFailure ]
+webkit.org/b/77568 fast/text/locale-shaping-complex.html [ ImageOnlyFailure ]
+
 ################################################################################
 ###########    End Missing Functionality Prevents Testing         ##############
 ################################################################################
diff --git a/Source/WTF/ChangeLog b/Source/WTF/ChangeLog
index fce9626..fd45cab 100644
--- a/Source/WTF/ChangeLog
+++ b/Source/WTF/ChangeLog
@@ -1,3 +1,13 @@
+2020-01-14  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        [Cocoa] Glyph lookup should be language-sensitive (specifically between Yiddish and Hebrew)
+        https://bugs.webkit.org/show_bug.cgi?id=77568
+        <rdar://problem/14649193>
+
+        Reviewed by Simon Fraser.
+
+        * wtf/Platform.h:
+
 2020-01-13  Darin Adler  <darin@apple.com>
 
         Use even more "shortest form" formatting, and less "fixed precision" and "fixed width"
diff --git a/Source/WTF/wtf/Platform.h b/Source/WTF/wtf/Platform.h
index c8a0afd..dad3825 100644
--- a/Source/WTF/wtf/Platform.h
+++ b/Source/WTF/wtf/Platform.h
@@ -917,6 +917,10 @@
 #define HAVE_CTFONTCREATEFORCHARACTERSWITHLANGUAGEANDOPTION 1
 #endif
 
+#if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 130000)
+#define HAVE_CTFONTTRANSFORMGLYPHSWITHLANGUAGE 1
+#endif
+
 #if PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 130000
 #define HAVE_ARKIT_QUICK_LOOK_PREVIEW_ITEM 1
 #endif
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index f7131d6..ef90cb9 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,58 @@
+2020-01-14  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        [Cocoa] Glyph lookup should be language-sensitive (specifically between Yiddish and Hebrew)
+        https://bugs.webkit.org/show_bug.cgi?id=77568
+        <rdar://problem/14649193>
+
+        Reviewed by Simon Fraser.
+
+        Switch from CTFontTransformGlyphs() to CTFontTransformGlyphsWithLanguage().
+
+        CTFontTransformGlyphsWithLanguage() accepts a callback when it needs to insert
+        glyphs and the glyph buffer isn't big enough. This patch hooks up this callback to
+        a "makeHole()" function which reallocs the glyph buffer so the hole can be filled in
+        by shaping.
+
+        We cache the CFDictionaries of the CFAttributedString we pass into CoreText using a
+        HashMap stored in Font.
+
+        Tests: fast/text/locale-shaping-complex.html
+               fast/text/locale-shaping.html
+
+        * platform/graphics/Font.cpp:
+        (WebCore::Font::applyTransforms const):
+        * platform/graphics/Font.h:
+        (WebCore::Font::CFStringAttributesKey::CFStringAttributesKey):
+        (WebCore::Font::CFStringAttributesKey::operator== const):
+        (WebCore::Font::CFStringAttributesKey::operator!= const):
+        (WebCore::Font::CFStringAttributesKey::isHashTableDeletedValue const):
+        (WebCore::Font::CFStringAttributesKey::computeHash const):
+        (WebCore::Font::CFStringAttributesKeyHash::hash):
+        (WebCore::Font::CFStringAttributesKeyHash::equal):
+        * platform/graphics/FontCascade.cpp:
+        (WebCore::FontCascade::widthForSimpleText const):
+        * platform/graphics/GlyphBuffer.h:
+        (WebCore::GlyphBuffer::isEmpty const):
+        (WebCore::GlyphBuffer::size const):
+        (WebCore::GlyphBuffer::clear):
+        (WebCore::GlyphBuffer::advances const):
+        (WebCore::GlyphBuffer::fontAt const):
+        (WebCore::GlyphBuffer::add):
+        (WebCore::GlyphBuffer::remove):
+        (WebCore::GlyphBuffer::makeHole):
+        (WebCore::GlyphBuffer::shrink):
+        (WebCore::GlyphBuffer::swap):
+        (WebCore::GlyphBuffer::advancesCount const): Deleted.
+        * platform/graphics/WidthIterator.cpp:
+        (WebCore::WidthIterator::applyFontTransforms):
+        * platform/graphics/WidthIterator.h:
+        * platform/graphics/cocoa/FontCocoa.mm:
+        (WebCore::Font::applyTransforms const):
+        * platform/graphics/mac/ComplexTextControllerCoreText.mm:
+        (WebCore::ComplexTextController::collectComplexTextRunsForCharacters):
+        * platform/graphics/mac/SimpleFontDataCoreText.cpp:
+        (WebCore::Font::getCFStringAttributes const):
+
 2020-01-14  Ross Kirsling  <ross.kirsling@sony.com>
 
         [PlayStation] Add standardUserAgentForURL stub
diff --git a/Source/WebCore/PAL/ChangeLog b/Source/WebCore/PAL/ChangeLog
index 9636e62..1679eb1 100644
--- a/Source/WebCore/PAL/ChangeLog
+++ b/Source/WebCore/PAL/ChangeLog
@@ -1,3 +1,13 @@
+2020-01-14  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        [Cocoa] Glyph lookup should be language-sensitive (specifically between Yiddish and Hebrew)
+        https://bugs.webkit.org/show_bug.cgi?id=77568
+        <rdar://problem/14649193>
+
+        Reviewed by Simon Fraser.
+
+        * pal/spi/cocoa/CoreTextSPI.h:
+
 2020-01-09  Eric Carlson  <eric.carlson@apple.com>
 
         Don't softlink AVCapture classes on watchOS or tvOS
diff --git a/Source/WebCore/PAL/pal/spi/cocoa/CoreTextSPI.h b/Source/WebCore/PAL/pal/spi/cocoa/CoreTextSPI.h
index da2a9d4..d0f81c2 100644
--- a/Source/WebCore/PAL/pal/spi/cocoa/CoreTextSPI.h
+++ b/Source/WebCore/PAL/pal/spi/cocoa/CoreTextSPI.h
@@ -92,6 +92,7 @@
 extern const CFStringRef kCTFontCSSFamilySystemUI;
 
 bool CTFontTransformGlyphs(CTFontRef, CGGlyph glyphs[], CGSize advances[], CFIndex count, CTFontTransformOptions);
+CGSize CTFontTransformGlyphsWithLanguage(CTFontRef, CGGlyph[], CGSize[], CFIndex count, CTFontTransformOptions, CFStringRef language, void (^handler)(CFRange, CGGlyph**, CGSize**));
 
 CGSize CTRunGetInitialAdvance(CTRunRef);
 CTLineRef CTLineCreateWithUniCharProvider(CTUniCharProviderCallback, CTUniCharDisposeCallback, void* refCon);
diff --git a/Source/WebCore/platform/graphics/Font.cpp b/Source/WebCore/platform/graphics/Font.cpp
index 8979b25..d945a96 100644
--- a/Source/WebCore/platform/graphics/Font.cpp
+++ b/Source/WebCore/platform/graphics/Font.cpp
@@ -499,20 +499,11 @@
     return platformCreateScaledFont(fontDescription, scaleFactor);
 }
 
-bool Font::applyTransforms(GlyphBufferGlyph* glyphs, GlyphBufferAdvance* advances, size_t glyphCount, bool enableKerning, bool requiresShaping) const
+#if !PLATFORM(COCOA)
+void Font::applyTransforms(GlyphBuffer&, unsigned, bool, bool, const AtomString&) const
 {
-#if PLATFORM(COCOA)
-    CTFontTransformOptions options = (enableKerning ? kCTFontTransformApplyPositioning : 0) | (requiresShaping ? kCTFontTransformApplyShaping : 0);
-    return CTFontTransformGlyphs(m_platformData.ctFont(), glyphs, reinterpret_cast<CGSize*>(advances), glyphCount, options);
-#else
-    UNUSED_PARAM(glyphs);
-    UNUSED_PARAM(advances);
-    UNUSED_PARAM(glyphCount);
-    UNUSED_PARAM(enableKerning);
-    UNUSED_PARAM(requiresShaping);
-    return false;
-#endif
 }
+#endif
 
 class CharacterFallbackMapKey {
 public:
diff --git a/Source/WebCore/platform/graphics/Font.h b/Source/WebCore/platform/graphics/Font.h
index 0c95259..03bf5ee 100644
--- a/Source/WebCore/platform/graphics/Font.h
+++ b/Source/WebCore/platform/graphics/Font.h
@@ -35,10 +35,12 @@
 #include "OpenTypeVerticalData.h"
 #endif
 #include <wtf/BitVector.h>
+#include <wtf/Hasher.h>
 #include <wtf/Optional.h>
 #include <wtf/text/StringHash.h>
 
 #if PLATFORM(COCOA)
+#include <CoreFoundation/CoreFoundation.h>
 #include <wtf/RetainPtr.h>
 #endif
 
@@ -196,7 +198,7 @@
 #endif
 #if PLATFORM(COCOA)
     CTFontRef getCTFont() const { return m_platformData.font(); }
-    CFDictionaryRef getCFStringAttributes(bool enableKerning, FontOrientation) const;
+    RetainPtr<CFDictionaryRef> getCFStringAttributes(bool enableKerning, FontOrientation, const AtomString& locale) const;
     const BitVector& glyphsSupportedBySmallCaps() const;
     const BitVector& glyphsSupportedByAllSmallCaps() const;
     const BitVector& glyphsSupportedByPetiteCaps() const;
@@ -208,7 +210,7 @@
 #endif
 
     bool canRenderCombiningCharacterSequence(const UChar*, size_t) const;
-    bool applyTransforms(GlyphBufferGlyph*, GlyphBufferAdvance*, size_t glyphCount, bool enableKerning, bool requiresShaping) const;
+    void applyTransforms(GlyphBuffer&, unsigned beginningIndex, bool enableKerning, bool requiresShaping, const AtomString& locale) const;
 
 #if PLATFORM(WIN)
     SCRIPT_FONTPROPERTIES* scriptFontProperties() const;
@@ -281,8 +283,6 @@
     mutable std::unique_ptr<DerivedFonts> m_derivedFontData;
 
 #if PLATFORM(COCOA)
-    mutable RetainPtr<CFMutableDictionaryRef> m_nonKernedCFStringAttributes;
-    mutable RetainPtr<CFMutableDictionaryRef> m_kernedCFStringAttributes;
     mutable Optional<BitVector> m_glyphsSupportedBySmallCaps;
     mutable Optional<BitVector> m_glyphsSupportedByAllSmallCaps;
     mutable Optional<BitVector> m_glyphsSupportedByPetiteCaps;
diff --git a/Source/WebCore/platform/graphics/FontCascade.cpp b/Source/WebCore/platform/graphics/FontCascade.cpp
index 8308f2b..5e5d038 100644
--- a/Source/WebCore/platform/graphics/FontCascade.cpp
+++ b/Source/WebCore/platform/graphics/FontCascade.cpp
@@ -416,6 +416,7 @@
     if (cacheEntry && !std::isnan(*cacheEntry))
         return *cacheEntry;
 
+    GlyphBuffer glyphBuffer;
     Vector<GlyphBufferGlyph, 16> glyphs;
     Vector<GlyphBufferAdvance, 16> advances;
     bool hasKerningOrLigatures = enableKerning() || requiresShaping();
@@ -427,16 +428,15 @@
         runWidth += glyphWidth;
         if (!hasKerningOrLigatures)
             continue;
-        glyphs.append(glyph);
-        advances.append(FloatSize(glyphWidth, 0));
+        glyphBuffer.add(glyph, &font, glyphWidth);
     }
     if (hasKerningOrLigatures) {
-        font.applyTransforms(&glyphs[0], &advances[0], glyphs.size(), enableKerning(), requiresShaping());
+        font.applyTransforms(glyphBuffer, 0, enableKerning(), requiresShaping(), fontDescription().locale());
         // This is needed only to match the result of the slow path. Same glyph widths but different floating point arithmentics can
         // produce different run width.
         float runWidthDifferenceWithTransformApplied = -runWidth;
-        for (auto& advance : advances)
-            runWidthDifferenceWithTransformApplied += advance.width();
+        for (size_t i = 0; i < glyphBuffer.size(); ++i)
+            runWidthDifferenceWithTransformApplied += glyphBuffer.advanceAt(i).width();
         runWidth += runWidthDifferenceWithTransformApplied;
     }
 
diff --git a/Source/WebCore/platform/graphics/GlyphBuffer.h b/Source/WebCore/platform/graphics/GlyphBuffer.h
index 7b56a37..db65233 100644
--- a/Source/WebCore/platform/graphics/GlyphBuffer.h
+++ b/Source/WebCore/platform/graphics/GlyphBuffer.h
@@ -100,12 +100,12 @@
 
 class GlyphBuffer {
 public:
-    bool isEmpty() const { return m_font.isEmpty(); }
-    unsigned size() const { return m_font.size(); }
+    bool isEmpty() const { return m_fonts.isEmpty(); }
+    unsigned size() const { return m_fonts.size(); }
     
     void clear()
     {
-        m_font.clear();
+        m_fonts.clear();
         m_glyphs.clear();
         m_advances.clear();
         if (m_offsetsInString)
@@ -116,9 +116,8 @@
     GlyphBufferAdvance* advances(unsigned from) { return m_advances.data() + from; }
     const GlyphBufferGlyph* glyphs(unsigned from) const { return m_glyphs.data() + from; }
     const GlyphBufferAdvance* advances(unsigned from) const { return m_advances.data() + from; }
-    size_t advancesCount() const { return m_advances.size(); }
 
-    const Font* fontAt(unsigned index) const { return m_font[index]; }
+    const Font* fontAt(unsigned index) const { return m_fonts[index]; }
 
     void setInitialAdvance(GlyphBufferAdvance initialAdvance) { m_initialAdvance = initialAdvance; }
     const GlyphBufferAdvance& initialAdvance() const { return m_initialAdvance; }
@@ -145,7 +144,7 @@
 
     void add(Glyph glyph, const Font* font, GlyphBufferAdvance advance, unsigned offsetInString)
     {
-        m_font.append(font);
+        m_fonts.append(font);
         m_glyphs.append(glyph);
 
         m_advances.append(advance);
@@ -154,6 +153,26 @@
             m_offsetsInString->append(offsetInString);
     }
 
+    void remove(unsigned location, unsigned length)
+    {
+        m_fonts.remove(location, length);
+        m_glyphs.remove(location, length);
+        m_advances.remove(location, length);
+        if (m_offsetsInString)
+            m_offsetsInString->remove(location, length);
+    }
+
+    void makeHole(unsigned location, unsigned length, const Font* font)
+    {
+        ASSERT(location <= size());
+
+        m_fonts.insertVector(location, Vector<const Font*>(length, font));
+        m_glyphs.insertVector(location, Vector<GlyphBufferGlyph>(length, 0xFFFF));
+        m_advances.insertVector(location, Vector<GlyphBufferAdvance>(length, GlyphBufferAdvance(0, 0)));
+        if (m_offsetsInString)
+            m_offsetsInString->insertVector(location, Vector<unsigned>(length, 0));
+    }
+
     void reverse(unsigned from, unsigned length)
     {
         for (unsigned i = from, end = from + length - 1; i < end; ++i, --end)
@@ -188,7 +207,7 @@
 
     void shrink(unsigned truncationPoint)
     {
-        m_font.shrink(truncationPoint);
+        m_fonts.shrink(truncationPoint);
         m_glyphs.shrink(truncationPoint);
         m_advances.shrink(truncationPoint);
         if (m_offsetsInString)
@@ -198,9 +217,9 @@
 private:
     void swap(unsigned index1, unsigned index2)
     {
-        const Font* f = m_font[index1];
-        m_font[index1] = m_font[index2];
-        m_font[index2] = f;
+        const Font* f = m_fonts[index1];
+        m_fonts[index1] = m_fonts[index2];
+        m_fonts[index2] = f;
 
         GlyphBufferGlyph g = m_glyphs[index1];
         m_glyphs[index1] = m_glyphs[index2];
@@ -211,7 +230,7 @@
         m_advances[index2] = s;
     }
 
-    Vector<const Font*, 2048> m_font;
+    Vector<const Font*, 2048> m_fonts;
     Vector<GlyphBufferGlyph, 2048> m_glyphs;
     Vector<GlyphBufferAdvance, 2048> m_advances;
     GlyphBufferAdvance m_initialAdvance;
diff --git a/Source/WebCore/platform/graphics/WidthIterator.cpp b/Source/WebCore/platform/graphics/WidthIterator.cpp
index bcc86af..6e46d42 100644
--- a/Source/WebCore/platform/graphics/WidthIterator.cpp
+++ b/Source/WebCore/platform/graphics/WidthIterator.cpp
@@ -98,7 +98,7 @@
     if (!glyphBuffer)
         return 0;
 
-    unsigned glyphBufferSize = glyphBuffer->size();
+    auto glyphBufferSize = glyphBuffer->size();
     if (!force && glyphBufferSize <= lastGlyphCount + 1) {
         lastGlyphCount = glyphBufferSize;
         return 0;
@@ -113,7 +113,8 @@
     if (!ltr)
         glyphBuffer->reverse(lastGlyphCount, glyphBufferSize - lastGlyphCount);
 
-    font->applyTransforms(glyphBuffer->glyphs(lastGlyphCount), advances + lastGlyphCount, glyphBufferSize - lastGlyphCount, m_enableKerning, m_requiresShaping);
+    font->applyTransforms(*glyphBuffer, lastGlyphCount, m_enableKerning, m_requiresShaping, m_font->fontDescription().locale());
+    glyphBufferSize = glyphBuffer->size();
 
     for (unsigned i = lastGlyphCount; i < glyphBufferSize; ++i)
         advances[i].setHeight(-advances[i].height());
@@ -121,8 +122,14 @@
     if (!ltr)
         glyphBuffer->reverse(lastGlyphCount, glyphBufferSize - lastGlyphCount);
 
+    // https://bugs.webkit.org/show_bug.cgi?id=206208: This is totally, 100%, furiously, utterly, frustratingly bogus.
+    // There is absolutely no guarantee that glyph indices before shaping have any relation at all with glyph indices after shaping.
+    // One of the fundamental things that shaping does is insert glyph all over the place.
     for (size_t i = 0; i < charactersTreatedAsSpace.size(); ++i) {
-        int spaceOffset = charactersTreatedAsSpace[i].first;
+        auto spaceOffset = charactersTreatedAsSpace[i].first;
+        // Shaping may have deleted the glyph.
+        if (spaceOffset >= glyphBufferSize)
+            continue;
         const OriginalAdvancesForCharacterTreatedAsSpace& originalAdvances = charactersTreatedAsSpace[i].second;
         if (spaceOffset && !originalAdvances.characterIsSpace)
             glyphBuffer->advances(spaceOffset - 1)->setWidth(originalAdvances.advanceBeforeCharacter);
diff --git a/Source/WebCore/platform/graphics/WidthIterator.h b/Source/WebCore/platform/graphics/WidthIterator.h
index c45fdb8..b2c509f 100644
--- a/Source/WebCore/platform/graphics/WidthIterator.h
+++ b/Source/WebCore/platform/graphics/WidthIterator.h
@@ -35,7 +35,7 @@
 struct GlyphData;
 struct OriginalAdvancesForCharacterTreatedAsSpace;
 
-typedef Vector<std::pair<int, OriginalAdvancesForCharacterTreatedAsSpace>, 64> CharactersTreatedAsSpace;
+typedef Vector<std::pair<unsigned, OriginalAdvancesForCharacterTreatedAsSpace>, 64> CharactersTreatedAsSpace;
 
 struct WidthIterator {
     WTF_MAKE_FAST_ALLOCATED;
diff --git a/Source/WebCore/platform/graphics/cocoa/FontCocoa.mm b/Source/WebCore/platform/graphics/cocoa/FontCocoa.mm
index de9a170..5a3352b 100644
--- a/Source/WebCore/platform/graphics/cocoa/FontCocoa.mm
+++ b/Source/WebCore/platform/graphics/cocoa/FontCocoa.mm
@@ -543,6 +543,29 @@
     return createDerivativeFont(scaledFont.get(), size, m_platformData.orientation(), fontTraits, m_platformData.syntheticBold(), m_platformData.syntheticOblique());
 }
 
+void Font::applyTransforms(GlyphBuffer& glyphBuffer, unsigned beginningIndex, bool enableKerning, bool requiresShaping, const AtomString& locale) const
+{
+    // FIXME: Implement GlyphBuffer initial advance.
+    CTFontTransformOptions options = (enableKerning ? kCTFontTransformApplyPositioning : 0) | (requiresShaping ? kCTFontTransformApplyShaping : 0);
+#if HAVE(CTFONTTRANSFORMGLYPHSWITHLANGUAGE)
+    auto handler = ^(CFRange range, CGGlyph** newGlyphsPointer, CGSize** newAdvancesPointer) {
+        range.location = std::min(std::max(range.location, static_cast<CFIndex>(0)), static_cast<CFIndex>(glyphBuffer.size()));
+        if (range.length < 0) {
+            range.length = std::min(range.location, -range.length);
+            range.location = range.location - range.length;
+            glyphBuffer.remove(beginningIndex + range.location, range.length);
+        } else
+            glyphBuffer.makeHole(beginningIndex + range.location, range.length, this);
+        *newGlyphsPointer = glyphBuffer.glyphs(beginningIndex);
+        *newAdvancesPointer = glyphBuffer.advances(beginningIndex);
+    };
+    CTFontTransformGlyphsWithLanguage(m_platformData.ctFont(), glyphBuffer.glyphs(beginningIndex), reinterpret_cast<CGSize*>(glyphBuffer.advances(beginningIndex)), glyphBuffer.size() - beginningIndex, options, locale.string().createCFString().get(), handler);
+#else
+    UNUSED_PARAM(locale);
+    CTFontTransformGlyphs(m_platformData.ctFont(), glyphBuffer.glyphs(beginningIndex), reinterpret_cast<CGSize*>(glyphBuffer.advances(beginningIndex)), glyphBuffer.size() - beginningIndex, options);
+#endif
+}
+
 static int extractNumber(CFNumberRef number)
 {
     int result = 0;
diff --git a/Source/WebCore/platform/graphics/mac/ComplexTextControllerCoreText.mm b/Source/WebCore/platform/graphics/mac/ComplexTextControllerCoreText.mm
index 558a7dd..f3b5c13 100644
--- a/Source/WebCore/platform/graphics/mac/ComplexTextControllerCoreText.mm
+++ b/Source/WebCore/platform/graphics/mac/ComplexTextControllerCoreText.mm
@@ -128,11 +128,11 @@
         font = m_font.fallbackRangesAt(0).fontForCharacter(baseCharacter);
         if (!font)
             font = &m_font.fallbackRangesAt(0).fontForFirstRange();
-        stringAttributes = adoptCF(CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, font->getCFStringAttributes(m_font.enableKerning(), font->platformData().orientation())));
+        stringAttributes = adoptCF(CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, font->getCFStringAttributes(m_font.enableKerning(), font->platformData().orientation(), m_font.fontDescription().locale()).get()));
         // We don't know which font should be used to render this grapheme cluster, so enable CoreText's fallback mechanism by using the CTFont which doesn't have CoreText's fallback disabled.
         CFDictionarySetValue(const_cast<CFMutableDictionaryRef>(stringAttributes.get()), kCTFontAttributeName, font->platformData().font());
     } else
-        stringAttributes = font->getCFStringAttributes(m_font.enableKerning(), font->platformData().orientation());
+        stringAttributes = font->getCFStringAttributes(m_font.enableKerning(), font->platformData().orientation(), m_font.fontDescription().locale());
 
     RetainPtr<CTLineRef> line;
 
diff --git a/Source/WebCore/platform/graphics/mac/SimpleFontDataCoreText.cpp b/Source/WebCore/platform/graphics/mac/SimpleFontDataCoreText.cpp
index 7103b69..8945124 100644
--- a/Source/WebCore/platform/graphics/mac/SimpleFontDataCoreText.cpp
+++ b/Source/WebCore/platform/graphics/mac/SimpleFontDataCoreText.cpp
@@ -32,15 +32,17 @@
 
 namespace WebCore {
 
-CFDictionaryRef Font::getCFStringAttributes(bool enableKerning, FontOrientation orientation) const
+RetainPtr<CFDictionaryRef> Font::getCFStringAttributes(bool enableKerning, FontOrientation orientation, const AtomString& locale) const
 {
-    auto& attributesDictionary = enableKerning ? m_kernedCFStringAttributes : m_nonKernedCFStringAttributes;
-    if (attributesDictionary)
-        return attributesDictionary.get();
-
-    attributesDictionary = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 4, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
+    auto attributesDictionary = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 4, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
 
     CFDictionarySetValue(attributesDictionary.get(), kCTFontAttributeName, platformData().ctFont());
+#if HAVE(CTFONTTRANSFORMGLYPHSWITHLANGUAGE)
+    if (!locale.isEmpty())
+        CFDictionarySetValue(attributesDictionary.get(), kCTLanguageAttributeName, locale.string().createCFString().get());
+#else
+    UNUSED_PARAM(locale);
+#endif
     auto paragraphStyle = adoptCF(CTParagraphStyleCreate(nullptr, 0));
     CTParagraphStyleSetCompositionLanguage(paragraphStyle.get(), kCTCompositionLanguageNone);
     CFDictionarySetValue(attributesDictionary.get(), kCTParagraphStyleAttributeName, paragraphStyle.get());
@@ -54,7 +56,7 @@
     if (orientation == FontOrientation::Vertical)
         CFDictionarySetValue(attributesDictionary.get(), kCTVerticalFormsAttributeName, kCFBooleanTrue);
 
-    return attributesDictionary.get();
+    return attributesDictionary;
 }
 
 #if HAVE(DISALLOWABLE_USER_INSTALLED_FONTS)