Title: [211382] trunk
Revision
211382
Author
mmaxfi...@apple.com
Date
2017-01-30 12:43:14 -0800 (Mon, 30 Jan 2017)

Log Message

Correct spacing regression on inter-element complex path shaping on some fonts
https://bugs.webkit.org/show_bug.cgi?id=166013

Reviewed by Simon Fraser.

Source/WebCore:

This patch brings the implementation of ComplexTextController in-line with the
design at https://trac.webkit.org/wiki/ComplexTextController. Previously,
ComplexTextController had a few problems:
- The total width computed by ComplexTextController didn't match the width if
you iterated over the entire string and added up the advances
- FontCascade::getGlyphsAndAdvancesForComplexText() tried to compensate for
the above by construing the concepts of paint advances as distinct from layout
advances
- Initial advances were considered part of layout sometimes and part of painting
other times, depending on which function reports the information
- For RTL runs, the wrong origin was added to the initial advance, and the origin
should have been subtracted instead of added

This patch exhaustively updates every function in ComplexTextController to be
consistent with the design linked to above. This design solves all of these
problems.

Tests: ComplexTextControllerTest.InitialAdvanceWithLeftRunInRTL
       ComplexTextControllerTest.InitialAdvanceInRTL
       ComplexTextControllerTest.InitialAdvanceWithLeftRunInLTR
       ComplexTextControllerTest.InitialAdvanceInLTR
       ComplexTextControllerTest.InitialAdvanceInRTLNoOrigins
       ComplexTextControllerTest.LeadingExpansion
       ComplexTextControllerTest.VerticalAdvances

* platform/graphics/GlyphBuffer.h:
(WebCore::GlyphBuffer::setLeadingExpansion): Deleted. No longer necessary.
(WebCore::GlyphBuffer::leadingExpansion): Deleted. Ditto.
* platform/graphics/cocoa/FontCascadeCocoa.mm:
(WebCore::FontCascade::adjustSelectionRectForComplexText): Removed use of
unnecessary leadingExpansion().
(WebCore::FontCascade::getGlyphsAndAdvancesForComplexText): This function needs
to compute paint advances, which means that it can't base this information off
of layout advances. This function uses the trick mentioned at the end of the
above link to compute the paint offset of an arbitrary glyph in the middle of
an RTL run.
* platform/graphics/mac/ComplexTextController.cpp:
(WebCore::ComplexTextController::computeExpansionOpportunity): Refactored for
testing.
(WebCore::ComplexTextController::ComplexTextController): Ditto.
(WebCore::ComplexTextController::finishConstruction): Ditto.
(WebCore::ComplexTextController::offsetForPosition): This function operates on
layout advances, and the initial layout advance is already added into the
m_adjustedBaseAdvances vector by adjustGlyphsAndAdvances(). Therefore, there is
no need to add it again here.
(WebCore::ComplexTextController::advance): This function had completely busted
logic about the relationship between initial advances and the first origin in
each run. Because of the fortunate choice of only representing layout advances
in m_adjustedBaseAdvances, this entire block can be removed and the raw paint
initial advance can be reported to the GlyphBuffer. Later in the function, we
have to update the logic about how to compute a paint advance given a layout
advance and some origins. In particular, there are two tricky pieces here: 1.
The layout advance for the first glyph is equal to (initial advance - first
origin + first Core Text advance, so computing the paint offset must cancel out
the initial layout offset, and 2. the last paint advance in a run must actually
end up at the position of the first glyph in the next run, so the next run's
initial advance must be queried.
(WebCore::ComplexTextController::adjustGlyphsAndAdvances): Previously, we
represented an initial advance of a successive run by just adding it to the
previous run's last advance. However, this is incompatible with the new model
presented in the link above, so we remove this section. We also have to add in
the logic that the layout advance for the first glyph is equal to the formula
presented above.
* platform/graphics/mac/ComplexTextController.h:
(WebCore::ComplexTextController::ComplexTextRun::initialAdvance): Adjust comment
to reflect reality.
(WebCore::ComplexTextController::leadingExpansion): Deleted.

Tools:

Unskip existing tests and make some new tests:
- Testing complex text with no origins
- Testing initial expansions
- Testing the sign of vertical advances

* TestWebKitAPI/Tests/WebCore/ComplexTextController.cpp:
(TestWebKitAPI::TEST_F):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (211381 => 211382)


--- trunk/Source/WebCore/ChangeLog	2017-01-30 20:08:29 UTC (rev 211381)
+++ trunk/Source/WebCore/ChangeLog	2017-01-30 20:43:14 UTC (rev 211382)
@@ -1,3 +1,78 @@
+2017-01-30  Myles C. Maxfield  <mmaxfi...@apple.com>
+
+        Correct spacing regression on inter-element complex path shaping on some fonts
+        https://bugs.webkit.org/show_bug.cgi?id=166013
+
+        Reviewed by Simon Fraser.
+
+        This patch brings the implementation of ComplexTextController in-line with the
+        design at https://trac.webkit.org/wiki/ComplexTextController. Previously,
+        ComplexTextController had a few problems:
+        - The total width computed by ComplexTextController didn't match the width if
+        you iterated over the entire string and added up the advances
+        - FontCascade::getGlyphsAndAdvancesForComplexText() tried to compensate for
+        the above by construing the concepts of paint advances as distinct from layout
+        advances
+        - Initial advances were considered part of layout sometimes and part of painting
+        other times, depending on which function reports the information
+        - For RTL runs, the wrong origin was added to the initial advance, and the origin
+        should have been subtracted instead of added
+
+        This patch exhaustively updates every function in ComplexTextController to be
+        consistent with the design linked to above. This design solves all of these
+        problems.
+
+        Tests: ComplexTextControllerTest.InitialAdvanceWithLeftRunInRTL
+               ComplexTextControllerTest.InitialAdvanceInRTL
+               ComplexTextControllerTest.InitialAdvanceWithLeftRunInLTR
+               ComplexTextControllerTest.InitialAdvanceInLTR
+               ComplexTextControllerTest.InitialAdvanceInRTLNoOrigins
+               ComplexTextControllerTest.LeadingExpansion
+               ComplexTextControllerTest.VerticalAdvances
+
+        * platform/graphics/GlyphBuffer.h:
+        (WebCore::GlyphBuffer::setLeadingExpansion): Deleted. No longer necessary.
+        (WebCore::GlyphBuffer::leadingExpansion): Deleted. Ditto.
+        * platform/graphics/cocoa/FontCascadeCocoa.mm:
+        (WebCore::FontCascade::adjustSelectionRectForComplexText): Removed use of
+        unnecessary leadingExpansion().
+        (WebCore::FontCascade::getGlyphsAndAdvancesForComplexText): This function needs
+        to compute paint advances, which means that it can't base this information off
+        of layout advances. This function uses the trick mentioned at the end of the
+        above link to compute the paint offset of an arbitrary glyph in the middle of
+        an RTL run.
+        * platform/graphics/mac/ComplexTextController.cpp:
+        (WebCore::ComplexTextController::computeExpansionOpportunity): Refactored for
+        testing.
+        (WebCore::ComplexTextController::ComplexTextController): Ditto.
+        (WebCore::ComplexTextController::finishConstruction): Ditto.
+        (WebCore::ComplexTextController::offsetForPosition): This function operates on
+        layout advances, and the initial layout advance is already added into the
+        m_adjustedBaseAdvances vector by adjustGlyphsAndAdvances(). Therefore, there is
+        no need to add it again here.
+        (WebCore::ComplexTextController::advance): This function had completely busted
+        logic about the relationship between initial advances and the first origin in
+        each run. Because of the fortunate choice of only representing layout advances 
+        in m_adjustedBaseAdvances, this entire block can be removed and the raw paint
+        initial advance can be reported to the GlyphBuffer. Later in the function, we
+        have to update the logic about how to compute a paint advance given a layout
+        advance and some origins. In particular, there are two tricky pieces here: 1.
+        The layout advance for the first glyph is equal to (initial advance - first
+        origin + first Core Text advance, so computing the paint offset must cancel out
+        the initial layout offset, and 2. the last paint advance in a run must actually
+        end up at the position of the first glyph in the next run, so the next run's
+        initial advance must be queried.
+        (WebCore::ComplexTextController::adjustGlyphsAndAdvances): Previously, we
+        represented an initial advance of a successive run by just adding it to the
+        previous run's last advance. However, this is incompatible with the new model
+        presented in the link above, so we remove this section. We also have to add in
+        the logic that the layout advance for the first glyph is equal to the formula
+        presented above.
+        * platform/graphics/mac/ComplexTextController.h:
+        (WebCore::ComplexTextController::ComplexTextRun::initialAdvance): Adjust comment
+        to reflect reality.
+        (WebCore::ComplexTextController::leadingExpansion): Deleted.
+
 2017-01-30  Simon Fraser  <simon.fra...@apple.com>
 
         Fixed elements should not rubber-band in WK2, nor remain at negative offsets

Modified: trunk/Source/WebCore/platform/graphics/GlyphBuffer.h (211381 => 211382)


--- trunk/Source/WebCore/platform/graphics/GlyphBuffer.h	2017-01-30 20:08:29 UTC (rev 211381)
+++ trunk/Source/WebCore/platform/graphics/GlyphBuffer.h	2017-01-30 20:43:14 UTC (rev 211382)
@@ -102,9 +102,6 @@
 
     void setInitialAdvance(GlyphBufferAdvance initialAdvance) { m_initialAdvance = initialAdvance; }
     const GlyphBufferAdvance& initialAdvance() const { return m_initialAdvance; }
-
-    void setLeadingExpansion(float leadingExpansion) { m_leadingExpansion = leadingExpansion; }
-    float leadingExpansion() const { return m_leadingExpansion; }
     
     Glyph glyphAt(unsigned index) const
     {
@@ -248,7 +245,6 @@
 #if PLATFORM(WIN)
     Vector<FloatSize, 2048> m_offsets;
 #endif
-    float m_leadingExpansion;
 };
 
 }

Modified: trunk/Source/WebCore/platform/graphics/cocoa/FontCascadeCocoa.mm (211381 => 211382)


--- trunk/Source/WebCore/platform/graphics/cocoa/FontCascadeCocoa.mm	2017-01-30 20:08:29 UTC (rev 211381)
+++ trunk/Source/WebCore/platform/graphics/cocoa/FontCascadeCocoa.mm	2017-01-30 20:43:14 UTC (rev 211382)
@@ -497,7 +497,7 @@
     float afterWidth = controller.runWidthSoFar();
 
     if (run.rtl())
-        selectionRect.move(controller.totalWidth() - afterWidth + controller.leadingExpansion(), 0);
+        selectionRect.move(controller.totalWidth() - afterWidth, 0);
     else
         selectionRect.move(beforeWidth, 0);
     selectionRect.setWidth(LayoutUnit::fromFloatCeil(afterWidth - beforeWidth));
@@ -508,20 +508,25 @@
     float initialAdvance;
 
     ComplexTextController controller(*this, run, false, 0, forTextEmphasis);
-    controller.advance(from);
-    float beforeWidth = controller.runWidthSoFar();
+    GlyphBuffer dummyGlyphBuffer;
+    controller.advance(from, &dummyGlyphBuffer);
     controller.advance(to, &glyphBuffer);
 
     if (glyphBuffer.isEmpty())
         return 0;
 
-    float afterWidth = controller.runWidthSoFar();
-
     if (run.rtl()) {
-        initialAdvance = controller.totalWidth() - afterWidth + controller.leadingExpansion();
+        // Exploit the fact that the sum of the paint advances is equal to
+        // the sum of the layout advances.
+        initialAdvance = controller.totalWidth();
+        for (unsigned i = 0; i < glyphBuffer.size(); ++i)
+            initialAdvance -= glyphBuffer.advanceAt(i).width();
         glyphBuffer.reverse(0, glyphBuffer.size());
-    } else
-        initialAdvance = beforeWidth;
+    } else {
+        initialAdvance = dummyGlyphBuffer.initialAdvance().width();
+        for (unsigned i = 0; i < dummyGlyphBuffer.size(); ++i)
+            initialAdvance += dummyGlyphBuffer.advanceAt(i).width();
+    }
 
     return initialAdvance;
 }

Modified: trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.cpp (211381 => 211382)


--- trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.cpp	2017-01-30 20:08:29 UTC (rev 211381)
+++ trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.cpp	2017-01-30 20:43:14 UTC (rev 211382)
@@ -107,19 +107,12 @@
     return layout.width(from, len, fallbackFonts);
 }
 
-ComplexTextController::ComplexTextController(const FontCascade& font, const TextRun& run, bool mayUseNaturalWritingDirection, HashSet<const Font*>* fallbackFonts, bool forTextEmphasis)
-    : m_fallbackFonts(fallbackFonts)
-    , m_font(font)
-    , m_run(run)
-    , m_end(run.length())
-    , m_expansion(run.expansion())
-    , m_mayUseNaturalWritingDirection(mayUseNaturalWritingDirection)
-    , m_forTextEmphasis(forTextEmphasis)
+void ComplexTextController::computeExpansionOpportunity()
 {
     if (!m_expansion)
         m_expansionPerOpportunity = 0;
     else {
-        unsigned expansionOpportunityCount = FontCascade::expansionOpportunityCount(m_run.text(), m_run.ltr() ? LTR : RTL, run.expansionBehavior()).first;
+        unsigned expansionOpportunityCount = FontCascade::expansionOpportunityCount(m_run.text(), m_run.ltr() ? LTR : RTL, m_run.expansionBehavior()).first;
 
         if (!expansionOpportunityCount)
             m_expansionPerOpportunity = 0;
@@ -126,7 +119,19 @@
         else
             m_expansionPerOpportunity = m_expansion / expansionOpportunityCount;
     }
+}
 
+ComplexTextController::ComplexTextController(const FontCascade& font, const TextRun& run, bool mayUseNaturalWritingDirection, HashSet<const Font*>* fallbackFonts, bool forTextEmphasis)
+    : m_fallbackFonts(fallbackFonts)
+    , m_font(font)
+    , m_run(run)
+    , m_end(run.length())
+    , m_expansion(run.expansion())
+    , m_mayUseNaturalWritingDirection(mayUseNaturalWritingDirection)
+    , m_forTextEmphasis(forTextEmphasis)
+{
+    computeExpansionOpportunity();
+
     collectComplexTextRuns();
 
     finishConstruction();
@@ -136,7 +141,10 @@
     : m_font(font)
     , m_run(run)
     , m_end(run.length())
+    , m_expansion(run.expansion())
 {
+    computeExpansionOpportunity();
+
     for (auto& run : runs)
         m_complexTextRuns.append(run.ptr());
 
@@ -157,8 +165,6 @@
             glyphCountSoFar += m_complexTextRuns[i]->glyphCount();
         }
     }
-
-    m_runWidthSoFar = m_leadingExpansion;
 }
 
 unsigned ComplexTextController::offsetForPosition(float h, bool includePartialGlyphs)
@@ -166,7 +172,6 @@
     if (h >= m_totalWidth)
         return m_run.ltr() ? m_end : 0;
 
-    h -= m_leadingExpansion;
     if (h < 0)
         return m_run.ltr() ? 0 : m_end;
 
@@ -180,8 +185,6 @@
         for (unsigned j = 0; j < complexTextRun.glyphCount(); ++j) {
             size_t index = offsetIntoAdjustedGlyphs + j;
             CGFloat adjustedAdvance = m_adjustedBaseAdvances[index].width;
-            if (!index)
-                adjustedAdvance += complexTextRun.initialAdvance().width;
             if (x < adjustedAdvance) {
                 CFIndex hitGlyphStart = complexTextRun.indexAt(j);
                 CFIndex hitGlyphEnd;
@@ -561,7 +564,7 @@
         offset = m_end;
 
     if (offset <= m_currentCharacter) {
-        m_runWidthSoFar = m_leadingExpansion;
+        m_runWidthSoFar = 0;
         m_numGlyphsSoFar = 0;
         m_currentRun = 0;
         m_glyphInCurrentRun = 0;
@@ -572,14 +575,14 @@
 
     size_t runCount = m_complexTextRuns.size();
 
-    unsigned leftmostGlyph = 0;
-    unsigned currentRunIndex = indexOfCurrentRun(leftmostGlyph);
+    unsigned indexOfLeftmostGlyphInCurrentRun = 0; // Relative to the beginning of ComplexTextController.
+    unsigned currentRunIndex = indexOfCurrentRun(indexOfLeftmostGlyphInCurrentRun);
     while (m_currentRun < runCount) {
         const ComplexTextRun& complexTextRun = *m_complexTextRuns[currentRunIndex];
         bool ltr = complexTextRun.isLTR();
         size_t glyphCount = complexTextRun.glyphCount();
-        unsigned g = ltr ? m_glyphInCurrentRun : glyphCount - 1 - m_glyphInCurrentRun;
-        unsigned k = leftmostGlyph + g;
+        unsigned glyphIndexIntoCurrentRun = ltr ? m_glyphInCurrentRun : glyphCount - 1 - m_glyphInCurrentRun;
+        unsigned glyphIndexIntoComplexTextController = indexOfLeftmostGlyphInCurrentRun + glyphIndexIntoCurrentRun;
         if (fallbackFonts && &complexTextRun.font() != &m_font.primaryFont())
             fallbackFonts->add(&complexTextRun.font());
 
@@ -586,43 +589,45 @@
         // We must store the initial advance for the first glyph we are going to draw.
         // When leftmostGlyph is 0, it represents the first glyph to draw, taking into
         // account the text direction.
-        if (glyphBuffer && !leftmostGlyph) {
-            CGSize initialAdvance = complexTextRun.initialAdvance();
-#if USE_LAYOUT_SPECIFIC_ADVANCES
-            unsigned index = ltr ? 0 : m_adjustedBaseAdvances.size() - 1;
-            initialAdvance.width += glyphOrigin(index).x;
-            initialAdvance.height += glyphOrigin(index).y;
-#endif
-            glyphBuffer->setInitialAdvance(initialAdvance);
+        if (!indexOfLeftmostGlyphInCurrentRun && glyphBuffer)
+            glyphBuffer->setInitialAdvance(complexTextRun.initialAdvance());
 
-            glyphBuffer->setLeadingExpansion(m_leadingExpansion);
-        }
-
         while (m_glyphInCurrentRun < glyphCount) {
-            unsigned glyphStartOffset = complexTextRun.indexAt(g);
+            unsigned glyphStartOffset = complexTextRun.indexAt(glyphIndexIntoCurrentRun);
             unsigned glyphEndOffset;
             if (complexTextRun.isMonotonic()) {
                 if (ltr)
-                    glyphEndOffset = std::max<unsigned>(glyphStartOffset, static_cast<unsigned>(g + 1 < glyphCount ? complexTextRun.indexAt(g + 1) : complexTextRun.indexEnd()));
+                    glyphEndOffset = std::max<unsigned>(glyphStartOffset, static_cast<unsigned>(glyphIndexIntoCurrentRun + 1 < glyphCount ? complexTextRun.indexAt(glyphIndexIntoCurrentRun + 1) : complexTextRun.indexEnd()));
                 else
-                    glyphEndOffset = std::max<unsigned>(glyphStartOffset, static_cast<unsigned>(g > 0 ? complexTextRun.indexAt(g - 1) : complexTextRun.indexEnd()));
+                    glyphEndOffset = std::max<unsigned>(glyphStartOffset, static_cast<unsigned>(glyphIndexIntoCurrentRun > 0 ? complexTextRun.indexAt(glyphIndexIntoCurrentRun - 1) : complexTextRun.indexEnd()));
             } else
-                glyphEndOffset = complexTextRun.endOffsetAt(g);
+                glyphEndOffset = complexTextRun.endOffsetAt(glyphIndexIntoCurrentRun);
 
-            CGSize adjustedBaseAdvance = m_adjustedBaseAdvances[k];
+            CGSize adjustedBaseAdvance = m_adjustedBaseAdvances[glyphIndexIntoComplexTextController];
 
             if (glyphStartOffset + complexTextRun.stringLocation() >= m_currentCharacter)
                 return;
 
             if (glyphBuffer && !m_characterInCurrentGlyph) {
+                auto currentGlyphOrigin = glyphOrigin(glyphIndexIntoComplexTextController);
                 GlyphBufferAdvance paintAdvance = adjustedBaseAdvance;
-#if USE_LAYOUT_SPECIFIC_ADVANCES
-                if (k + 1 < m_adjustedBaseAdvances.size()) {
-                    paintAdvance.setWidth(paintAdvance.width() + glyphOrigin(k + 1).x - glyphOrigin(k).x);
-                    paintAdvance.setHeight(paintAdvance.height() - glyphOrigin(k + 1).y + glyphOrigin(k).y);
+                if (!glyphIndexIntoCurrentRun) {
+                    // The first layout advance of every run includes the "initial layout advance." However, here, we need
+                    // paint advances, so subtract it out before transforming the layout advance into a paint advance.
+                    paintAdvance.setWidth(paintAdvance.width() - (complexTextRun.initialAdvance().width - currentGlyphOrigin.x));
+                    paintAdvance.setHeight(paintAdvance.height() - (complexTextRun.initialAdvance().height - currentGlyphOrigin.y));
                 }
-#endif
-                glyphBuffer->add(m_adjustedGlyphs[k], &complexTextRun.font(), paintAdvance, complexTextRun.indexAt(m_glyphInCurrentRun));
+                paintAdvance.setWidth(paintAdvance.width() + glyphOrigin(glyphIndexIntoComplexTextController + 1).x - currentGlyphOrigin.x);
+                paintAdvance.setHeight(paintAdvance.height() + glyphOrigin(glyphIndexIntoComplexTextController + 1).y - currentGlyphOrigin.y);
+                if (glyphIndexIntoCurrentRun == glyphCount - 1 && currentRunIndex + 1 < runCount) {
+                    // Our paint advance points to the end of the run. However, the next run may have an
+                    // initial advance, and our paint advance needs to point to the location of the next
+                    // glyph. So, we need to add in the next run's initial advance.
+                    paintAdvance.setWidth(paintAdvance.width() - glyphOrigin(glyphIndexIntoComplexTextController + 1).x + m_complexTextRuns[currentRunIndex + 1]->initialAdvance().width);
+                    paintAdvance.setHeight(paintAdvance.height() - glyphOrigin(glyphIndexIntoComplexTextController + 1).y + m_complexTextRuns[currentRunIndex + 1]->initialAdvance().height);
+                }
+                paintAdvance.setHeight(-paintAdvance.height()); // Increasing y points down
+                glyphBuffer->add(m_adjustedGlyphs[glyphIndexIntoComplexTextController], &complexTextRun.font(), paintAdvance, complexTextRun.indexAt(m_glyphInCurrentRun));
             }
 
             unsigned oldCharacterInCurrentGlyph = m_characterInCurrentGlyph;
@@ -636,14 +641,14 @@
             m_glyphInCurrentRun++;
             m_characterInCurrentGlyph = 0;
             if (ltr) {
-                g++;
-                k++;
+                glyphIndexIntoCurrentRun++;
+                glyphIndexIntoComplexTextController++;
             } else {
-                g--;
-                k--;
+                glyphIndexIntoCurrentRun--;
+                glyphIndexIntoComplexTextController--;
             }
         }
-        currentRunIndex = incrementCurrentRun(leftmostGlyph);
+        currentRunIndex = incrementCurrentRun(indexOfLeftmostGlyphInCurrentRun);
         m_glyphInCurrentRun = 0;
     }
 }
@@ -690,16 +695,6 @@
         unsigned glyphCount = complexTextRun.glyphCount();
         const Font& font = complexTextRun.font();
 
-        // Represent the initial advance for a text run by adjusting the advance
-        // of the last glyph of the previous text run in the glyph buffer.
-        if (!complexTextRun.glyphOrigins() && runIndex && m_adjustedBaseAdvances.size()) {
-            CGSize previousAdvance = m_adjustedBaseAdvances.last();
-            previousAdvance.width += complexTextRun.initialAdvance().width;
-            previousAdvance.height -= complexTextRun.initialAdvance().height;
-            m_adjustedBaseAdvances[m_adjustedBaseAdvances.size() - 1] = previousAdvance;
-        }
-        widthSinceLastCommit += complexTextRun.initialAdvance().width;
-
         if (!complexTextRun.isLTR())
             m_isLTROnly = false;
 
@@ -743,6 +738,15 @@
                 glyph = font.spaceGlyph();
             }
 
+            if (!i) {
+                advance.width += complexTextRun.initialAdvance().width;
+                advance.height += complexTextRun.initialAdvance().height;
+                if (auto* origins = complexTextRun.glyphOrigins()) {
+                    advance.width -= origins[0].x;
+                    advance.height -= origins[0].y;
+                }
+            }
+
             advance.width += font.syntheticBoldOffset();
 
             if (hasExtraSpacing) {
@@ -774,20 +778,17 @@
                     if (m_expansion) {
                         bool expandLeft, expandRight;
                         std::tie(expandLeft, expandRight) = expansionLocation(ideograph, treatAsSpace, m_run.ltr(), afterExpansion, forbidLeadingExpansion, forbidTrailingExpansion, forceLeadingExpansion, forceTrailingExpansion);
+                        m_expansion -= m_expansionPerOpportunity;
+                        advance.width += m_expansionPerOpportunity;
                         if (expandLeft) {
                             // Increase previous width
-                            m_expansion -= m_expansionPerOpportunity;
-                            m_totalWidth += m_expansionPerOpportunity;
                             if (m_adjustedBaseAdvances.isEmpty())
-                                m_leadingExpansion = m_expansionPerOpportunity;
+                                complexTextRun.growInitialAdvanceHorizontally(m_expansionPerOpportunity);
                             else
                                 m_adjustedBaseAdvances.last().width += m_expansionPerOpportunity;
                         }
-                        if (expandRight) {
-                            m_expansion -= m_expansionPerOpportunity;
-                            advance.width += m_expansionPerOpportunity;
+                        if (expandRight)
                             afterExpansion = true;
-                        }
                     } else
                         afterExpansion = false;
 
@@ -804,9 +805,7 @@
             if (m_forTextEmphasis && (!FontCascade::canReceiveTextEmphasis(ch) || (U_GET_GC_MASK(ch) & U_GC_M_MASK)))
                 glyph = 0;
 
-            advance.height *= -1;
             m_adjustedBaseAdvances.append(advance);
-#if USE_LAYOUT_SPECIFIC_ADVANCES
             if (auto* origins = complexTextRun.glyphOrigins()) {
                 ASSERT(m_glyphOrigins.size() < m_adjustedBaseAdvances.size());
                 m_glyphOrigins.grow(m_adjustedBaseAdvances.size());
@@ -813,7 +812,6 @@
                 m_glyphOrigins[m_glyphOrigins.size() - 1] = origins[i];
                 ASSERT(m_glyphOrigins.size() == m_adjustedBaseAdvances.size());
             }
-#endif
             m_adjustedGlyphs.append(glyph);
             
             FloatRect glyphBounds = font.boundsForGlyph(glyph);

Modified: trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.h (211381 => 211382)


--- trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.h	2017-01-30 20:08:29 UTC (rev 211381)
+++ trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.h	2017-01-30 20:43:14 UTC (rev 211382)
@@ -51,8 +51,7 @@
 
 enum GlyphIterationStyle { IncludePartialGlyphs, ByWholeGlyphs };
 
-// ComplexTextController is responsible for rendering and measuring glyphs for
-// complex scripts on macOS and iOS.
+// See https://trac.webkit.org/wiki/ComplexTextController for more information about ComplexTextController.
 class ComplexTextController {
     WTF_MAKE_FAST_ALLOCATED;
 public:
@@ -77,8 +76,6 @@
     float minGlyphBoundingBoxY() const { return m_minGlyphBoundingBoxY; }
     float maxGlyphBoundingBoxY() const { return m_maxGlyphBoundingBoxY; }
 
-    float leadingExpansion() const { return m_leadingExpansion; }
-
     class ComplexTextRun : public RefCounted<ComplexTextRun> {
     public:
         static Ref<ComplexTextRun> create(CTRunRef ctRun, const Font& font, const UChar* characters, unsigned stringLocation, size_t stringLength, CFRange runRange)
@@ -108,15 +105,33 @@
         const CGGlyph* glyphs() const { return m_glyphs; }
 
         /*
-         *                                              X (Paint glyph position)   X (Paint glyph position)   X (Paint glyph position)
-         *                                             7                          7                          7
-         *                                            /                          /                          /
-         *                                           / (Glyph origin)           / (Glyph origin)           / (Glyph origin)
-         *                                          /                          /                          /
-         *                                         /                          /                          /
-         *                X-----------------------X--------------------------X--------------------------X---->...
-         * (text position ^)  (initial advance)          (base advance)             (base advance)
+         * This is the format of the information CoreText gives us about each run:
+         *
+         *                                        ----->X (Paint glyph position)   X (Paint glyph position)   X (Paint glyph position)
+         *                                       /     7                          7                          7
+         *                                      /     /                          /                          /
+         *                   (Initial advance) /     / (Glyph origin)           / (Glyph origin)           / (Glyph origin)
+         *                  -------------------     /                          /                          /
+         *                 /                       /                          /                          /
+         *                X                       X--------------------------X--------------------------X--------------------------X
+         * (text position ^)                             (base advance)             (base advance)             (base advance)
+         *
+         *
+         *
+         *
+         *
+         * And here is the output we transform this into (for each run):
+         *
+         *                                        ----->X------------------------->X------------------------->X
+         *                                       /            (Paint advance)            (Paint advance)       \
+         *                                      /                                                               \
+         *                   (Initial advance) /                                                                 \ (Paint advance)
+         *                  -------------------                                                                   ----------------
+         *                 /                                                                                                      \
+         *                X--------------------------------------------------X--------------------------X--------------------------X
+         * (text position ^)                (layout advance)                       (layout advance)           (layout advance)
          */
+        void growInitialAdvanceHorizontally(CGFloat delta) { m_initialAdvance.width += delta; }
         CGSize initialAdvance() const { return m_initialAdvance; }
         const CGSize* baseAdvances() const { return m_baseAdvances; }
         const CGPoint* glyphOrigins() const { return m_glyphOrigins.size() == glyphCount() ? m_glyphOrigins.data() : nullptr; }
@@ -149,6 +164,7 @@
         bool m_isMonotonic { true };
     };
 private:
+    void computeExpansionOpportunity();
     void finishConstruction();
     
     static unsigned stringBegin(const ComplexTextRun& run) { return run.stringLocation() + run.indexBegin(); }
@@ -206,7 +222,6 @@
     unsigned m_characterInCurrentGlyph { 0 };
     float m_expansion { 0 };
     float m_expansionPerOpportunity { 0 };
-    float m_leadingExpansion { 0 };
 
     float m_minGlyphBoundingBoxX { std::numeric_limits<float>::max() };
     float m_maxGlyphBoundingBoxX { std::numeric_limits<float>::min() };

Modified: trunk/Tools/ChangeLog (211381 => 211382)


--- trunk/Tools/ChangeLog	2017-01-30 20:08:29 UTC (rev 211381)
+++ trunk/Tools/ChangeLog	2017-01-30 20:43:14 UTC (rev 211382)
@@ -1,3 +1,18 @@
+2017-01-30  Myles C. Maxfield  <mmaxfi...@apple.com>
+
+        Correct spacing regression on inter-element complex path shaping on some fonts
+        https://bugs.webkit.org/show_bug.cgi?id=166013
+
+        Reviewed by Simon Fraser.
+
+        Unskip existing tests and make some new tests:
+        - Testing complex text with no origins
+        - Testing initial expansions
+        - Testing the sign of vertical advances
+
+        * TestWebKitAPI/Tests/WebCore/ComplexTextController.cpp:
+        (TestWebKitAPI::TEST_F):
+
 2017-01-30  Carlos Alberto Lopez Perez  <clo...@igalia.com>
 
         [GTK][EFL] Avoid using a thin directory to create the built product on the archive-built-product step.

Modified: trunk/Tools/TestWebKitAPI/Tests/WebCore/ComplexTextController.cpp (211381 => 211382)


--- trunk/Tools/TestWebKitAPI/Tests/WebCore/ComplexTextController.cpp	2017-01-30 20:08:29 UTC (rev 211381)
+++ trunk/Tools/TestWebKitAPI/Tests/WebCore/ComplexTextController.cpp	2017-01-30 20:43:14 UTC (rev 211382)
@@ -45,7 +45,7 @@
     }
 };
 
-TEST_F(ComplexTextControllerTest, DISABLED_InitialAdvanceWithLeftRunInRTL)
+TEST_F(ComplexTextControllerTest, InitialAdvanceWithLeftRunInRTL)
 {
     FontCascadeDescription description;
     description.setOneFamily("Times");
@@ -79,6 +79,7 @@
         totalWidth += advances[i].width;
     EXPECT_NEAR(controller.totalWidth(), spaceWidth + totalWidth, 0.0001);
     GlyphBuffer glyphBuffer;
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
     controller.advance(0, &glyphBuffer);
     EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
     controller.advance(1, &glyphBuffer);
@@ -96,7 +97,7 @@
     EXPECT_NEAR(glyphBuffer.advanceAt(5).width(), spaceWidth + initialAdvance.width, 0.0001);
 }
 
-TEST_F(ComplexTextControllerTest, DISABLED_InitialAdvanceInRTL)
+TEST_F(ComplexTextControllerTest, InitialAdvanceInRTL)
 {
     FontCascadeDescription description;
     description.setOneFamily("Times");
@@ -127,6 +128,7 @@
         totalWidth += advances[i].width;
     EXPECT_NEAR(controller.totalWidth(), totalWidth, 0.0001);
     GlyphBuffer glyphBuffer;
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
     controller.advance(0, &glyphBuffer);
     EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
     controller.advance(1, &glyphBuffer);
@@ -141,9 +143,10 @@
     EXPECT_NEAR(glyphBuffer.advanceAt(2).width(), advances[2].width, 0.0001);
     EXPECT_NEAR(glyphBuffer.advanceAt(3).width(), advances[1].width, 0.0001);
     EXPECT_NEAR(glyphBuffer.advanceAt(4).width(), -initialAdvance.width, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(4).height(), initialAdvance.height, 0.0001);
 }
 
-TEST_F(ComplexTextControllerTest, DISABLED_InitialAdvanceWithLeftRunInLTR)
+TEST_F(ComplexTextControllerTest, InitialAdvanceWithLeftRunInLTR)
 {
     FontCascadeDescription description;
     description.setOneFamily("LucidaGrande");
@@ -174,6 +177,7 @@
 
     EXPECT_NEAR(controller.totalWidth(), spaceWidth + 76.347656 + initialAdvance.width, 0.0001);
     GlyphBuffer glyphBuffer;
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
     controller.advance(0, &glyphBuffer);
     EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
     controller.advance(1, &glyphBuffer);
@@ -190,7 +194,7 @@
     EXPECT_NEAR(glyphBuffer.advanceAt(2).width(), 23.281250, 0.0001);
 }
 
-TEST_F(ComplexTextControllerTest, DISABLED_InitialAdvanceInLTR)
+TEST_F(ComplexTextControllerTest, InitialAdvanceInLTR)
 {
     FontCascadeDescription description;
     description.setOneFamily("LucidaGrande");
@@ -218,6 +222,7 @@
 
     EXPECT_NEAR(controller.totalWidth(), 76.347656 + initialAdvance.width, 0.0001);
     GlyphBuffer glyphBuffer;
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
     controller.advance(0, &glyphBuffer);
     EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
     controller.advance(1, &glyphBuffer);
@@ -231,4 +236,124 @@
     EXPECT_NEAR(glyphBuffer.advanceAt(1).width(), 23.281250, 0.0001);
 }
 
+TEST_F(ComplexTextControllerTest, InitialAdvanceInRTLNoOrigins)
+{
+    FontCascadeDescription description;
+    description.setOneFamily("Times");
+    description.setComputedSize(48);
+    FontCascade font(description);
+    font.update();
+
+    CGSize initialAdvance = CGSizeMake(4.33996383363472, 12.368896925859);
+
+    UChar characters[] = { 0x633, 0x20, 0x627, 0x650 };
+    size_t charactersLength = WTF_ARRAY_LENGTH(characters);
+    TextRun textRun(StringView(characters, charactersLength));
+    auto run1 = ComplexTextController::ComplexTextRun::createForTesting({ CGSizeMake(-4.33996383363472, -12.368896925859), CGSizeMake(14.0397830018083, 0) }, { }, { 884, 240 }, { 3, 2 }, initialAdvance, font.primaryFont(), characters, 0, charactersLength, CFRangeMake(2, 2), false);
+    auto run2 = ComplexTextController::ComplexTextRun::createForTesting({ CGSizeMake(12.0, 0) }, { }, { 3 }, { 1 }, CGSizeZero, font.primaryFont(), characters, 0, charactersLength, CFRangeMake(1, 1), false);
+    auto run3 = ComplexTextController::ComplexTextRun::createForTesting({ CGSizeMake(43.8119349005425, 0) }, { }, { 276 }, { 0 }, CGSizeZero, font.primaryFont(), characters, 0, charactersLength, CFRangeMake(0, 1), false);
+    Vector<Ref<ComplexTextController::ComplexTextRun>> runs;
+    runs.append(WTFMove(run1));
+    runs.append(WTFMove(run2));
+    runs.append(WTFMove(run3));
+    ComplexTextController controller(font, textRun, runs);
+
+    CGFloat totalWidth = 14.0397830018083 + 12.0 + 43.8119349005425;
+    EXPECT_NEAR(controller.totalWidth(), totalWidth, 0.0001);
+    GlyphBuffer glyphBuffer;
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
+    controller.advance(0, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
+    controller.advance(1, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), 43.8119349005425, 0.0001);
+    controller.advance(2, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), 43.8119349005425 + 12.0, 0.0001);
+    controller.advance(3, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), totalWidth, 0.0001);
+    controller.advance(4, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), totalWidth, 0.0001);
+    EXPECT_NEAR(glyphBuffer.initialAdvance().width(), initialAdvance.width, 0.0001);
+    EXPECT_NEAR(glyphBuffer.initialAdvance().height(), initialAdvance.height, 0.0001);
+    EXPECT_EQ(glyphBuffer.size(), 4U);
+    EXPECT_NEAR(glyphBuffer.advanceAt(0).width(), 43.8119349005425, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(1).width(), 12.0, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(2).width(), 14.0397830018083, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(3).width(), -4.33996383363472, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(3).height(), 12.368896925859, 0.0001);
 }
+
+TEST_F(ComplexTextControllerTest, LeadingExpansion)
+{
+    FontCascadeDescription description;
+    description.setOneFamily("Times");
+    description.setComputedSize(48);
+    FontCascade font(description);
+    font.update();
+
+    UChar characters[] = { 'a' };
+    size_t charactersLength = WTF_ARRAY_LENGTH(characters);
+    TextRun textRun(StringView(characters, charactersLength), 0, 100, ForceLeadingExpansion);
+    auto run = ComplexTextController::ComplexTextRun::createForTesting({ CGSizeMake(24, 0) }, { }, { 16 }, { 0 }, CGSizeZero, font.primaryFont(), characters, 0, charactersLength, CFRangeMake(0, 1), true);
+    Vector<Ref<ComplexTextController::ComplexTextRun>> runs;
+    runs.append(WTFMove(run));
+    ComplexTextController controller(font, textRun, runs);
+
+    CGFloat totalWidth = 100 + 24;
+    EXPECT_NEAR(controller.totalWidth(), totalWidth, 0.0001);
+    GlyphBuffer glyphBuffer;
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
+    controller.advance(0, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
+    controller.advance(1, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), totalWidth, 0.0001);
+    EXPECT_NEAR(glyphBuffer.initialAdvance().width(), 100, 0.0001);
+    EXPECT_NEAR(glyphBuffer.initialAdvance().height(), 0, 0.0001);
+    EXPECT_EQ(glyphBuffer.size(), 1U);
+    EXPECT_NEAR(glyphBuffer.advanceAt(0).width(), 24, 0.0001);
+}
+
+TEST_F(ComplexTextControllerTest, VerticalAdvances)
+{
+    FontCascadeDescription description;
+    description.setOneFamily("Times");
+    description.setComputedSize(48);
+    FontCascade font(description);
+    font.update();
+
+    UChar characters[] = { 'a', 'b', 'c', 'd' };
+    size_t charactersLength = WTF_ARRAY_LENGTH(characters);
+    TextRun textRun(StringView(characters, charactersLength));
+    auto run1 = ComplexTextController::ComplexTextRun::createForTesting({ CGSizeMake(0, 1), CGSizeMake(0, 2) }, { CGPointMake(0, 4), CGPointMake(0, 8) }, { 16, 17 }, { 0, 1 }, CGSizeMake(0, 16), font.primaryFont(), characters, 0, charactersLength, CFRangeMake(0, 2), true);
+    auto run2 = ComplexTextController::ComplexTextRun::createForTesting({ CGSizeMake(0, 32), CGSizeMake(0, 64) }, { CGPointMake(0, 128), CGPointMake(0, 256) }, { 18, 19 }, { 2, 3 }, CGSizeMake(0, 512), font.primaryFont(), characters, 0, charactersLength, CFRangeMake(2, 2), true);
+    Vector<Ref<ComplexTextController::ComplexTextRun>> runs;
+    runs.append(WTFMove(run1));
+    runs.append(WTFMove(run2));
+    ComplexTextController controller(font, textRun, runs);
+
+    EXPECT_NEAR(controller.totalWidth(), 0, 0.0001);
+    GlyphBuffer glyphBuffer;
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
+    controller.advance(0, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
+    controller.advance(1, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
+    controller.advance(2, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
+    controller.advance(3, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
+    controller.advance(4, &glyphBuffer);
+    EXPECT_NEAR(controller.runWidthSoFar(), 0, 0.0001);
+    EXPECT_NEAR(glyphBuffer.initialAdvance().width(), 0, 0.0001);
+    EXPECT_NEAR(glyphBuffer.initialAdvance().height(), 16, 0.0001);
+    EXPECT_EQ(glyphBuffer.size(), 4U);
+    EXPECT_NEAR(glyphBuffer.advanceAt(0).width(), 0, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(0).height(), 4 - 1 -8, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(1).width(), 0, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(1).height(), 8 - 2 - 512, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(2).width(), 0, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(2).height(), 128 - 32 - 256, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(3).width(), 0, 0.0001);
+    EXPECT_NEAR(glyphBuffer.advanceAt(3).height(), 256 - 64, 0.0001);
+}
+
+}
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to