Title: [268561] trunk/Source/WebCore
Revision
268561
Author
mmaxfi...@apple.com
Date
2020-10-15 16:51:06 -0700 (Thu, 15 Oct 2020)

Log Message

[Cocoa] Don't change the text matrix multiple times inside FontCascade::drawGlyphs()
https://bugs.webkit.org/show_bug.cgi?id=217749

Reviewed by Simon Fraser.

FontCascade::drawGlyphs() may actually call CTFontDrawGlyphs() 4 times:
2 for synthetic bold, times 2 for emulated shadows that we draw ourselves.
Previously, each of these calls can have a different text matrix, because
showGlyphsWithAdvances() called CGContextSetTextPosition(), which affects
the text matrix.

This makes https://bugs.webkit.org/show_bug.cgi?id=217506 difficult, because
we don't actually track the text matrix inside the GraphicsContext. In that
patch, we call FontCascade::drawGlyphs() and intercept the resulting CGContext
calls. However, if we detect a modified text matrix inside those CGContext
calls, we have no way of knowing whether it was modified by WebKit or
internally inside Core Text. We need to know this, though, because the
modifications Core Text makes have to be preserved across to the GPU process,
but the modifications WebKit makes need to not be preserved (since the GPU
process will make them naturally).

Tracking the text matrix in the CGContext (a la the CTM) is unnecessary; all
we need to know is whether WebKit changed it or Core Text changed it. Therefore,
this patch migrates all the text matrix computation into standalone functions
that can be called from GPU Process infrastructure, so we can know what WebKit
does there (and also what Core Text does, by omission).

This only works, though, if the text matrix is identical for all the calls to
CTFontDrawGlyphs() inside FontCascade::drawGlyphs(). This patch enforces this
by emulating CGContextSetTextPosition() ourselves by adding a constant to
all the glyph positions. Performance is not a conern because we already iterate
over all the glyphs inside showGlyphsWithAdvances(), so we're already paying
the cost of the loop.

No new tests because there is no behavior change.

* platform/graphics/FontCascade.h:
(WebCore::FontCascade::syntheticObliqueAngle): Deleted.
* platform/graphics/cocoa/FontCascadeCocoa.mm:
(WebCore::showLetterpressedGlyphsWithAdvances):
* platform/graphics/coretext/FontCascadeCoreText.cpp:
(WebCore::rotateLeftTransform):
(WebCore::fillVectorWithHorizontalGlyphPositions):
(WebCore::fillVectorWithVerticalGlyphPositions):
(WebCore::showGlyphsWithAdvances):
(WebCore::computeOverallTextMatrix):
(WebCore::computeVerticalTextMatrix):
(WebCore::FontCascade::drawGlyphs):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (268560 => 268561)


--- trunk/Source/WebCore/ChangeLog	2020-10-15 23:41:51 UTC (rev 268560)
+++ trunk/Source/WebCore/ChangeLog	2020-10-15 23:51:06 UTC (rev 268561)
@@ -1,3 +1,54 @@
+2020-10-15  Myles C. Maxfield  <mmaxfi...@apple.com>
+
+        [Cocoa] Don't change the text matrix multiple times inside FontCascade::drawGlyphs()
+        https://bugs.webkit.org/show_bug.cgi?id=217749
+
+        Reviewed by Simon Fraser.
+
+        FontCascade::drawGlyphs() may actually call CTFontDrawGlyphs() 4 times:
+        2 for synthetic bold, times 2 for emulated shadows that we draw ourselves.
+        Previously, each of these calls can have a different text matrix, because
+        showGlyphsWithAdvances() called CGContextSetTextPosition(), which affects
+        the text matrix.
+
+        This makes https://bugs.webkit.org/show_bug.cgi?id=217506 difficult, because
+        we don't actually track the text matrix inside the GraphicsContext. In that
+        patch, we call FontCascade::drawGlyphs() and intercept the resulting CGContext
+        calls. However, if we detect a modified text matrix inside those CGContext
+        calls, we have no way of knowing whether it was modified by WebKit or
+        internally inside Core Text. We need to know this, though, because the
+        modifications Core Text makes have to be preserved across to the GPU process,
+        but the modifications WebKit makes need to not be preserved (since the GPU
+        process will make them naturally). 
+
+        Tracking the text matrix in the CGContext (a la the CTM) is unnecessary; all
+        we need to know is whether WebKit changed it or Core Text changed it. Therefore,
+        this patch migrates all the text matrix computation into standalone functions
+        that can be called from GPU Process infrastructure, so we can know what WebKit
+        does there (and also what Core Text does, by omission).
+
+        This only works, though, if the text matrix is identical for all the calls to
+        CTFontDrawGlyphs() inside FontCascade::drawGlyphs(). This patch enforces this
+        by emulating CGContextSetTextPosition() ourselves by adding a constant to
+        all the glyph positions. Performance is not a conern because we already iterate
+        over all the glyphs inside showGlyphsWithAdvances(), so we're already paying
+        the cost of the loop.
+
+        No new tests because there is no behavior change.
+
+        * platform/graphics/FontCascade.h:
+        (WebCore::FontCascade::syntheticObliqueAngle): Deleted.
+        * platform/graphics/cocoa/FontCascadeCocoa.mm:
+        (WebCore::showLetterpressedGlyphsWithAdvances):
+        * platform/graphics/coretext/FontCascadeCoreText.cpp:
+        (WebCore::rotateLeftTransform):
+        (WebCore::fillVectorWithHorizontalGlyphPositions):
+        (WebCore::fillVectorWithVerticalGlyphPositions):
+        (WebCore::showGlyphsWithAdvances):
+        (WebCore::computeOverallTextMatrix):
+        (WebCore::computeVerticalTextMatrix):
+        (WebCore::FontCascade::drawGlyphs):
+
 2020-10-15  Chris Dumez  <cdu...@apple.com>
 
         Cache JS arguments in AudioWorkletProcessor::process() for performance

Modified: trunk/Source/WebCore/platform/graphics/FontCascade.h (268560 => 268561)


--- trunk/Source/WebCore/platform/graphics/FontCascade.h	2020-10-15 23:41:51 UTC (rev 268560)
+++ trunk/Source/WebCore/platform/graphics/FontCascade.h	2020-10-15 23:51:06 UTC (rev 268561)
@@ -83,7 +83,9 @@
 
 #if USE(CORE_TEXT)
 void showLetterpressedGlyphsWithAdvances(const FloatPoint&, const Font&, GraphicsContext&, const CGGlyph*, const CGSize* advances, unsigned count);
-void fillVectorWithHorizontalGlyphPositions(Vector<CGPoint, 256>& positions, CGContextRef, const CGSize* advances, unsigned count);
+void fillVectorWithHorizontalGlyphPositions(Vector<CGPoint, 256>& positions, CGContextRef, const CGSize* advances, unsigned count, const FloatPoint&);
+AffineTransform computeOverallTextMatrix(const Font&);
+AffineTransform computeVerticalTextMatrix(const Font&, const AffineTransform& previousTextMatrix);
 #endif
 
 class TextLayoutDeleter {
@@ -194,6 +196,8 @@
 
     bool primaryFontIsSystemFont() const;
 
+    static int syntheticObliqueAngle() { return 14; }
+
     std::unique_ptr<DisplayList::DisplayList> displayListForTextRun(GraphicsContext&, const TextRun&, unsigned from = 0, Optional<unsigned> to = { }, CustomFontNotReadyAction = CustomFontNotReadyAction::DoNotPaintIfFontNotReady) const;
 
 #if PLATFORM(WIN) && USE(CG)
@@ -307,8 +311,6 @@
         return advancedTextRenderingMode();
     }
 
-    static int syntheticObliqueAngle() { return 14; }
-
 #if PLATFORM(WIN) && USE(CG)
     static double s_fontSmoothingContrast;
     static uint32_t s_fontSmoothingType;

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


--- trunk/Source/WebCore/platform/graphics/cocoa/FontCascadeCocoa.mm	2020-10-15 23:41:51 UTC (rev 268560)
+++ trunk/Source/WebCore/platform/graphics/cocoa/FontCascadeCocoa.mm	2020-10-15 23:51:06 UTC (rev 268561)
@@ -62,9 +62,8 @@
 
     CGContextRef context = coreContext.platformContext();
 
-    CGContextSetTextPosition(context, point.x(), point.y());
     Vector<CGPoint, 256> positions(count);
-    fillVectorWithHorizontalGlyphPositions(positions, context, advances, count);
+    fillVectorWithHorizontalGlyphPositions(positions, context, advances, count, point);
 
     CTFontRef ctFont = platformData.ctFont();
     CGContextSetFontSize(context, CTFontGetSize(ctFont));

Modified: trunk/Source/WebCore/platform/graphics/coretext/FontCascadeCoreText.cpp (268560 => 268561)


--- trunk/Source/WebCore/platform/graphics/coretext/FontCascadeCoreText.cpp	2020-10-15 23:41:51 UTC (rev 268560)
+++ trunk/Source/WebCore/platform/graphics/coretext/FontCascadeCoreText.cpp	2020-10-15 23:51:06 UTC (rev 268561)
@@ -61,10 +61,16 @@
 #endif
 }
 
-void fillVectorWithHorizontalGlyphPositions(Vector<CGPoint, 256>& positions, CGContextRef context, const CGSize* advances, unsigned count)
+static const AffineTransform& rotateLeftTransform()
 {
+    static AffineTransform result(0, -1, 1, 0, 0, 0);
+    return result;
+}
+
+void fillVectorWithHorizontalGlyphPositions(Vector<CGPoint, 256>& positions, CGContextRef context, const CGSize* advances, unsigned count, const FloatPoint& point)
+{
     CGAffineTransform matrix = CGAffineTransformInvert(CGContextGetTextMatrix(context));
-    positions[0] = CGPointZero;
+    positions[0] = CGPointApplyAffineTransform(point, matrix);
     for (unsigned i = 1; i < count; ++i) {
         CGSize advance = CGSizeApplyAffineTransform(advances[i - 1], matrix);
         positions[i].x = positions[i - 1].x + advance.width;
@@ -72,6 +78,19 @@
     }
 }
 
+static void fillVectorWithVerticalGlyphPositions(Vector<CGPoint, 256>& positions, CGContextRef context, const CGSize* translations, const CGSize* advances, unsigned count, const FloatPoint& point, float ascentDelta)
+{
+    CGAffineTransform transform = CGAffineTransformInvert(CGContextGetTextMatrix(context));
+
+    auto position = CGPointMake(point.x(), point.y() + ascentDelta);
+    for (unsigned i = 0; i < count; ++i) {
+        CGSize translation = CGSizeApplyAffineTransform(translations[i], rotateLeftTransform());
+        positions[i] = CGPointApplyAffineTransform(CGPointMake(position.x - translation.width, position.y + translation.height), transform);
+        position.x += advances[i].width;
+        position.y += advances[i].height;
+    }
+}
+
 static inline bool shouldUseLetterpressEffect(const GraphicsContext& context)
 {
 #if ENABLE(LETTERPRESS)
@@ -82,36 +101,24 @@
 #endif
 }
 
-static void showGlyphsWithAdvances(const FloatPoint& point, const Font& font, CGContextRef context, const CGGlyph* glyphs, const CGSize* advances, unsigned count)
+static void showGlyphsWithAdvances(const FloatPoint& point, const Font& font, CGContextRef context, const CGGlyph* glyphs, const CGSize* advances, unsigned count, const AffineTransform& textMatrix)
 {
     if (!count)
         return;
 
-    CGContextSetTextPosition(context, point.x(), point.y());
-
     const FontPlatformData& platformData = font.platformData();
     Vector<CGPoint, 256> positions(count);
     if (platformData.orientation() == FontOrientation::Vertical) {
-        CGAffineTransform rotateLeftTransform = CGAffineTransformMake(0, -1, 1, 0, 0, 0);
-        CGAffineTransform textMatrix = CGContextGetTextMatrix(context);
-        CGAffineTransform runMatrix = CGAffineTransformConcat(textMatrix, rotateLeftTransform);
-        ScopedTextMatrix savedMatrix(runMatrix, context);
+        ScopedTextMatrix savedMatrix(computeVerticalTextMatrix(font, textMatrix), context);
 
         Vector<CGSize, 256> translations(count);
         CTFontGetVerticalTranslationsForGlyphs(platformData.ctFont(), glyphs, translations.data(), count);
 
-        CGAffineTransform transform = CGAffineTransformInvert(CGContextGetTextMatrix(context));
-
-        CGPoint position = FloatPoint(point.x(), point.y() + font.fontMetrics().floatAscent(IdeographicBaseline) - font.fontMetrics().floatAscent());
-        for (unsigned i = 0; i < count; ++i) {
-            CGSize translation = CGSizeApplyAffineTransform(translations[i], rotateLeftTransform);
-            positions[i] = CGPointApplyAffineTransform(CGPointMake(position.x - translation.width, position.y + translation.height), transform);
-            position.x += advances[i].width;
-            position.y += advances[i].height;
-        }
+        auto ascentDelta = font.fontMetrics().floatAscent(IdeographicBaseline) - font.fontMetrics().floatAscent();
+        fillVectorWithVerticalGlyphPositions(positions, context, translations.data(), advances, count, point, ascentDelta);
         CTFontDrawGlyphs(platformData.ctFont(), glyphs, positions.data(), count, context);
     } else {
-        fillVectorWithHorizontalGlyphPositions(positions, context, advances, count);
+        fillVectorWithHorizontalGlyphPositions(positions, context, advances, count, point);
         CTFontDrawGlyphs(platformData.ctFont(), glyphs, positions.data(), count, context);
     }
 }
@@ -130,9 +137,47 @@
     CGContextSetShouldSubpixelQuantizeFonts(cgContext, doSubpixelQuantization);
 }
 
+AffineTransform computeOverallTextMatrix(const Font& font)
+{
+    auto& platformData = font.platformData();
+    AffineTransform result;
+    if (!platformData.isColorBitmapFont())
+        result = CTFontGetMatrix(platformData.font());
+    result.setB(-result.b());
+    result.setD(-result.d());
+    if (platformData.syntheticOblique()) {
+        float obliqueSkew = tanf(FontCascade::syntheticObliqueAngle() * piFloat / 180);
+        if (platformData.orientation() == FontOrientation::Vertical) {
+            if (font.isTextOrientationFallback())
+                result = AffineTransform(1, obliqueSkew, 0, 1, 0, 0) * result;
+            else
+                result = AffineTransform(1, -obliqueSkew, 0, 1, 0, 0) * result;
+        } else
+            result = AffineTransform(1, 0, -obliqueSkew, 1, 0, 0);
+    }
+
+    // We're emulating the behavior of CGContextSetTextPosition() by adding constant amounts to each glyph's position
+    // (see fillVectorWithHorizontalGlyphPositions() and fillVectorWithVerticalGlyphPositions()).
+    // CGContextSetTextPosition() has the side effect of clobbering the E and F fields of the text matrix,
+    // so we do that explicitly here.
+    result.setE(0);
+    result.setF(0);
+    return result;
+}
+
+AffineTransform computeVerticalTextMatrix(const Font& font, const AffineTransform& previousTextMatrix)
+{
+    ASSERT_UNUSED(font, font.platformData().orientation() == FontOrientation::Vertical);
+    // The translation here ("e" and "f" fields) are irrelevant, because
+    // this matrix is inverted in fillVectorWithVerticalGlyphPositions to place the glyphs in the CTM's coordinate system.
+    // All we're trying to do here is rotate the text matrix so glyphs appear visually upright.
+    // We have to include the previous text matrix because it includes things like synthetic oblique.
+    return rotateLeftTransform() * previousTextMatrix;
+}
+
 void FontCascade::drawGlyphs(GraphicsContext& context, const Font& font, const GlyphBuffer& glyphBuffer, unsigned from, unsigned numGlyphs, const FloatPoint& anchorPoint, FontSmoothingMode smoothingMode)
 {
-    const FontPlatformData& platformData = font.platformData();
+    const auto& platformData = font.platformData();
     if (!platformData.size())
         return;
 
@@ -175,22 +220,8 @@
     UNUSED_VARIABLE(useLetterpressEffect);
     FloatPoint point = anchorPoint;
 
-    CGAffineTransform matrix = CGAffineTransformIdentity;
-    if (!platformData.isColorBitmapFont())
-        matrix = CTFontGetMatrix(platformData.font());
-    matrix.b = -matrix.b;
-    matrix.d = -matrix.d;
-    if (platformData.syntheticOblique()) {
-        static float obliqueSkew = tanf(syntheticObliqueAngle() * piFloat / 180);
-        if (platformData.orientation() == FontOrientation::Vertical) {
-            if (font.isTextOrientationFallback())
-                matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, obliqueSkew, 0, 1, 0, 0));
-            else
-                matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, -obliqueSkew, 0, 1, 0, 0));
-        } else
-            matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, 0, -obliqueSkew, 1, 0, 0));
-    }
-    ScopedTextMatrix restorer(matrix, cgContext);
+    auto textMatrix = computeOverallTextMatrix(font);
+    ScopedTextMatrix restorer(textMatrix, cgContext);
 
     setCGFontRenderingMode(context);
     CGContextSetFontSize(cgContext, platformData.size());
@@ -221,9 +252,9 @@
         float shadowTextX = point.x() + shadowOffset.width();
         // If shadows are ignoring transforms, then we haven't applied the Y coordinate flip yet, so down is negative.
         float shadowTextY = point.y() + shadowOffset.height() * (context.shadowsIgnoreTransforms() ? -1 : 1);
-        showGlyphsWithAdvances(FloatPoint(shadowTextX, shadowTextY), font, cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs);
+        showGlyphsWithAdvances(FloatPoint(shadowTextX, shadowTextY), font, cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs, textMatrix);
         if (syntheticBoldOffset)
-            showGlyphsWithAdvances(FloatPoint(shadowTextX + syntheticBoldOffset, shadowTextY), font, cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs);
+            showGlyphsWithAdvances(FloatPoint(shadowTextX + syntheticBoldOffset, shadowTextY), font, cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs, textMatrix);
         context.setFillColor(fillColor);
     }
 
@@ -233,11 +264,11 @@
     else
 #endif
     {
-        showGlyphsWithAdvances(point, font, cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs);
+        showGlyphsWithAdvances(point, font, cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs, textMatrix);
     }
 
     if (syntheticBoldOffset)
-        showGlyphsWithAdvances(FloatPoint(point.x() + syntheticBoldOffset, point.y()), font, cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs);
+        showGlyphsWithAdvances(FloatPoint(point.x() + syntheticBoldOffset, point.y()), font, cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs, textMatrix);
 
     if (hasSimpleShadow)
         context.setShadow(shadowOffset, shadowBlur, shadowColor);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to