Title: [286889] trunk
Revision
286889
Author
[email protected]
Date
2021-12-10 16:53:37 -0800 (Fri, 10 Dec 2021)

Log Message

[Cocoa] OT-SVG glyphs don't draw into canvases (because of the GPU process)
https://bugs.webkit.org/show_bug.cgi?id=234171
<rdar://problem/70166552>

Reviewed by Devin Rousso.

Source/WebCore:

Drawing OT-SVG glyphs into canvas was intentionally disabled in https://trac.webkit.org/changeset/269211/webkit.
This patch enables it again. Rather than doing anything complicated like supporting all of SVG in DrawGlyphsRecorder,
we can simply support this by drawing the glyphs into a ImageBuffer and sending the ImageBuffer to the GPU process.

For text, it's pretty important that the pixel grid of the ImageBuffer matches the pixel grid of the destination,
rather than being offset by half a pixel or something. This patch adds a new creation function to ImageBuffer which
accepts a FloatRect (instead of the previous FloatSize which it used to accept). The FloatRect is necessary because
inflating the geometry has to happen on both the left and the right if we want the pixel grids to match.

Test: fast/text/otsvg-canvas.html

* platform/graphics/DrawGlyphsRecorder.h:
* platform/graphics/ImageBuffer.cpp:
(WebCore::ImageBuffer::createCompatibleBuffer):
(WebCore::ImageBuffer::compatibleBufferInfo):
* platform/graphics/ImageBuffer.h:
* platform/graphics/coretext/DrawGlyphsRecorderCoreText.cpp:
(WebCore::DrawGlyphsRecorder::drawOTSVGRun):
(WebCore::DrawGlyphsRecorder::drawNonOTSVGRun):
(WebCore::DrawGlyphsRecorder::drawBySplittingIntoOTSVGAndNonOTSVGRuns):
(WebCore::DrawGlyphsRecorder::drawGlyphs):
(WebCore::filterOutOTSVGGlyphs): Deleted.

LayoutTests:

* fast/text/otsvg-canvas-expected.html: Added.
* fast/text/otsvg-canvas.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (286888 => 286889)


--- trunk/LayoutTests/ChangeLog	2021-12-11 00:53:30 UTC (rev 286888)
+++ trunk/LayoutTests/ChangeLog	2021-12-11 00:53:37 UTC (rev 286889)
@@ -1,3 +1,14 @@
+2021-12-10  Myles C. Maxfield  <[email protected]>
+
+        [Cocoa] OT-SVG glyphs don't draw into canvases (because of the GPU process)
+        https://bugs.webkit.org/show_bug.cgi?id=234171
+        <rdar://problem/70166552>
+
+        Reviewed by Devin Rousso.
+
+        * fast/text/otsvg-canvas-expected.html: Added.
+        * fast/text/otsvg-canvas.html: Added.
+
 2021-12-10  Robert Jenner  <[email protected]>
 
         REGRESSION(r286795):REBASELINE [ iOS EWS ] 4X CSS (layout-tests) are constant text failures

Added: trunk/LayoutTests/fast/text/otsvg-canvas-expected.html (0 => 286889)


--- trunk/LayoutTests/fast/text/otsvg-canvas-expected.html	                        (rev 0)
+++ trunk/LayoutTests/fast/text/otsvg-canvas-expected.html	2021-12-11 00:53:37 UTC (rev 286889)
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+@font-face {
+    font-family: "WebFont";
+    src: url("resources/Ahem-SVG.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+<p>
+This test makes sure that OT-SVG glyphs can be drawn into canvas.
+The test passes if you see:
+<ol>
+    <li>A medium-sized black square, then</li>
+    <li>A slightly smaller green square, then</li>
+    <li>A medium-sized black square, then</li>
+    <li>A slightly smaller green square</li>
+</p>
+<canvas id="canvas" width="400" height="400"></canvas>
+<script>
+var context = document.getElementById("canvas").getContext("2d");
+context.fillStyle = "black";
+context.fillRect(10, 60, 50, 50);
+context.fillStyle = "green";
+context.fillRect(65, 65, 40, 40);
+context.fillStyle = "black";
+context.fillRect(110, 60, 50, 50);
+context.fillStyle = "green";
+context.fillRect(165, 65, 40, 40);
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/fast/text/otsvg-canvas.html (0 => 286889)


--- trunk/LayoutTests/fast/text/otsvg-canvas.html	                        (rev 0)
+++ trunk/LayoutTests/fast/text/otsvg-canvas.html	2021-12-11 00:53:37 UTC (rev 286889)
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+@font-face {
+    font-family: "WebFont";
+    src: url("resources/Ahem-SVG.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+<p>
+This test makes sure that OT-SVG glyphs can be drawn into canvas.
+The test passes if you see:
+<ol>
+    <li>A medium-sized black square, then</li>
+    <li>A slightly smaller green square, then</li>
+    <li>A medium-sized black square, then</li>
+    <li>A slightly smaller green square</li>
+</p>
+<canvas id="canvas" width="400" height="400"></canvas>
+<script>
+var context = document.getElementById("canvas").getContext("2d");
+[...document.fonts][0].load().then(function() {
+    context.font = "50px 'WebFont'";
+    context.fillText("bAbA", 10, 100);
+});
+</script>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (286888 => 286889)


--- trunk/Source/WebCore/ChangeLog	2021-12-11 00:53:30 UTC (rev 286888)
+++ trunk/Source/WebCore/ChangeLog	2021-12-11 00:53:37 UTC (rev 286889)
@@ -1,3 +1,34 @@
+2021-12-10  Myles C. Maxfield  <[email protected]>
+
+        [Cocoa] OT-SVG glyphs don't draw into canvases (because of the GPU process)
+        https://bugs.webkit.org/show_bug.cgi?id=234171
+        <rdar://problem/70166552>
+
+        Reviewed by Devin Rousso.
+
+        Drawing OT-SVG glyphs into canvas was intentionally disabled in https://trac.webkit.org/changeset/269211/webkit.
+        This patch enables it again. Rather than doing anything complicated like supporting all of SVG in DrawGlyphsRecorder,
+        we can simply support this by drawing the glyphs into a ImageBuffer and sending the ImageBuffer to the GPU process.
+
+        For text, it's pretty important that the pixel grid of the ImageBuffer matches the pixel grid of the destination,
+        rather than being offset by half a pixel or something. This patch adds a new creation function to ImageBuffer which
+        accepts a FloatRect (instead of the previous FloatSize which it used to accept). The FloatRect is necessary because
+        inflating the geometry has to happen on both the left and the right if we want the pixel grids to match.
+
+        Test: fast/text/otsvg-canvas.html
+
+        * platform/graphics/DrawGlyphsRecorder.h:
+        * platform/graphics/ImageBuffer.cpp:
+        (WebCore::ImageBuffer::createCompatibleBuffer):
+        (WebCore::ImageBuffer::compatibleBufferInfo):
+        * platform/graphics/ImageBuffer.h:
+        * platform/graphics/coretext/DrawGlyphsRecorderCoreText.cpp:
+        (WebCore::DrawGlyphsRecorder::drawOTSVGRun):
+        (WebCore::DrawGlyphsRecorder::drawNonOTSVGRun):
+        (WebCore::DrawGlyphsRecorder::drawBySplittingIntoOTSVGAndNonOTSVGRuns):
+        (WebCore::DrawGlyphsRecorder::drawGlyphs):
+        (WebCore::filterOutOTSVGGlyphs): Deleted.
+
 2021-12-10  Megan Gardner  <[email protected]>
 
         Image does not update after Markup Pane is dismissed.

Modified: trunk/Source/WebCore/platform/graphics/DrawGlyphsRecorder.h (286888 => 286889)


--- trunk/Source/WebCore/platform/graphics/DrawGlyphsRecorder.h	2021-12-11 00:53:30 UTC (rev 286888)
+++ trunk/Source/WebCore/platform/graphics/DrawGlyphsRecorder.h	2021-12-11 00:53:37 UTC (rev 286889)
@@ -75,6 +75,10 @@
     UniqueRef<GraphicsContext> createInternalContext();
 #endif
 
+    void drawBySplittingIntoOTSVGAndNonOTSVGRuns(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned numGlyphs, const FloatPoint& anchorPoint, FontSmoothingMode);
+    void drawOTSVGRun(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned numGlyphs, const FloatPoint& anchorPoint, FontSmoothingMode);
+    void drawNonOTSVGRun(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned numGlyphs, const FloatPoint& anchorPoint, FontSmoothingMode);
+
     void populateInternalState(const GraphicsContextState&);
     void populateInternalContext(const GraphicsContextState&);
     void prepareInternalContext(const Font&, FontSmoothingMode);

Modified: trunk/Source/WebCore/platform/graphics/ImageBuffer.cpp (286888 => 286889)


--- trunk/Source/WebCore/platform/graphics/ImageBuffer.cpp	2021-12-11 00:53:30 UTC (rev 286888)
+++ trunk/Source/WebCore/platform/graphics/ImageBuffer.cpp	2021-12-11 00:53:37 UTC (rev 286889)
@@ -90,11 +90,37 @@
     if (!imageBuffer)
         return nullptr;
 
-    // Set up a corresponding scale factor on the graphics context.
     imageBuffer->context().scale(scaledSize / size);
     return imageBuffer;
 }
 
+// This is useful when you need to make sure the pixel grid of the ImageBuffer aligns with the pixel grid of the context.
+// Simply saying createCompatibleBuffer(rect.size(), context) isn't sufficient in this situation, because rect.location() may not lie on a pixel boundary.
+// In this situation, we have to inflate both the left side and the right side, which can lead to different results than
+// createCompatibleBuffer(rect.size(), context) would have produced.
+auto ImageBuffer::createCompatibleBuffer(const FloatRect& rect, const GraphicsContext& context) -> std::optional<CompatibleBufferDescription>
+{
+    if (rect.isEmpty())
+        return std::nullopt;
+
+    auto info = ImageBuffer::compatibleBufferInfo(rect, context);
+
+    RefPtr<ImageBuffer> imageBuffer;
+
+    if (context.renderingMode() == RenderingMode::Accelerated)
+        imageBuffer = AcceleratedImageBuffer::create(info.physicalSizeInDeviceCoordinates, context);
+    
+    if (!imageBuffer)
+        imageBuffer = UnacceleratedImageBuffer::create(info.physicalSizeInDeviceCoordinates, context);
+
+    if (!imageBuffer)
+        return std::nullopt;
+
+    imageBuffer->context().scale(info.scale);
+    imageBuffer->context().translate(-info.inflatedRectInUserCoordinates.location());
+    return { { imageBuffer.releaseNonNull(), info.inflatedRectInUserCoordinates } };
+}
+
 RefPtr<ImageBuffer> ImageBuffer::createCompatibleBuffer(const FloatSize& size, const DestinationColorSpace& colorSpace, const GraphicsContext& context)
 {
     if (size.isEmpty())
@@ -106,7 +132,6 @@
     if (!imageBuffer)
         return nullptr;
 
-    // Set up a corresponding scale factor on the graphics context.
     imageBuffer->context().scale(scaledSize / size);
     return imageBuffer;
 }
@@ -167,6 +192,36 @@
     return expandedIntSize(size * context.scaleFactor());
 }
 
+auto ImageBuffer::compatibleBufferInfo(const FloatRect& rect, const GraphicsContext& context) -> CompatibleBufferInfo
+{
+    auto scaleFactor = context.scaleFactor();
+    auto scaledRect = rect;
+    scaledRect.scale(scaleFactor);
+    auto inflatedScaledRect = enclosingIntRect(scaledRect);
+    auto inflatedRectInUserCoordinates = FloatRect(inflatedScaledRect);
+
+    // We don't want to allocate huge ImageBuffers because they can take a lot of memory,
+    // so if the size would have been too big, decrease resolution and scale the resulting context.
+    // This will mean that the ImageBuffer will lose quality, but will still generally look correct.
+    constexpr int maxDimension = 4096;
+    if (inflatedScaledRect.width() > maxDimension) {
+        // The pixel grids won't align any more, so there's no need to try to handle fractions of pixels.
+        inflatedScaledRect.setWidth(maxDimension);
+        inflatedRectInUserCoordinates.scale(maxDimension / inflatedRectInUserCoordinates.width(), 1);
+        scaleFactor.setWidth(maxDimension / rect.width());
+    }
+    if (inflatedScaledRect.height() > maxDimension) {
+        // The pixel grids won't align any more, so there's no need to try to handle fractions of pixels.
+        inflatedScaledRect.setHeight(maxDimension);
+        inflatedRectInUserCoordinates.scale(1, maxDimension / inflatedRectInUserCoordinates.height());
+        scaleFactor.setHeight(maxDimension / rect.height());
+    }
+
+    auto physicalSizeInDeviceCoordinates = inflatedScaledRect.size();
+    inflatedRectInUserCoordinates.scale(1.0f / scaleFactor);
+    return { WTFMove(physicalSizeInDeviceCoordinates), WTFMove(inflatedRectInUserCoordinates), scaleFactor };
+}
+
 RefPtr<ImageBuffer> ImageBuffer::copyRectToBuffer(const FloatRect& rect, const DestinationColorSpace& colorSpace, const GraphicsContext& context)
 {
     if (rect.isEmpty())

Modified: trunk/Source/WebCore/platform/graphics/ImageBuffer.h (286888 => 286889)


--- trunk/Source/WebCore/platform/graphics/ImageBuffer.h	2021-12-11 00:53:30 UTC (rev 286888)
+++ trunk/Source/WebCore/platform/graphics/ImageBuffer.h	2021-12-11 00:53:37 UTC (rev 286889)
@@ -52,6 +52,11 @@
 
     // Create an image buffer compatible with the context, with suitable resolution for drawing into the buffer and then into this context.
     static RefPtr<ImageBuffer> createCompatibleBuffer(const FloatSize&, const GraphicsContext&);
+    struct CompatibleBufferDescription {
+        Ref<ImageBuffer> imageBuffer;
+        FloatRect inflatedRectInUserCoordinates;
+    };
+    static std::optional<CompatibleBufferDescription> createCompatibleBuffer(const FloatRect& rectInUserCoordinates, const GraphicsContext&);
     WEBCORE_EXPORT static RefPtr<ImageBuffer> createCompatibleBuffer(const FloatSize&, const DestinationColorSpace&, const GraphicsContext&);
     static RefPtr<ImageBuffer> createCompatibleBuffer(const FloatSize&, float resolutionScale, const DestinationColorSpace&, const GraphicsContext&);
 
@@ -63,6 +68,12 @@
     static FloatRect clampedRect(const FloatRect&);
 
     static IntSize compatibleBufferSize(const FloatSize&, const GraphicsContext&);
+    struct CompatibleBufferInfo {
+        IntSize physicalSizeInDeviceCoordinates;
+        FloatRect inflatedRectInUserCoordinates;
+        FloatSize scale;
+    };
+    static CompatibleBufferInfo compatibleBufferInfo(const FloatRect&, const GraphicsContext&);
     
     WEBCORE_EXPORT virtual ~ImageBuffer() = default;
 

Modified: trunk/Source/WebCore/platform/graphics/coretext/DrawGlyphsRecorderCoreText.cpp (286888 => 286889)


--- trunk/Source/WebCore/platform/graphics/coretext/DrawGlyphsRecorderCoreText.cpp	2021-12-11 00:53:30 UTC (rev 286888)
+++ trunk/Source/WebCore/platform/graphics/coretext/DrawGlyphsRecorderCoreText.cpp	2021-12-11 00:53:37 UTC (rev 286889)
@@ -34,6 +34,7 @@
 #include "FontPlatformData.h"
 #include "GlyphBuffer.h"
 #include "GraphicsContextCG.h"
+#include "ImageBuffer.h"
 
 #include <CoreText/CoreText.h>
 #include <wtf/Vector.h>
@@ -370,53 +371,65 @@
     m_owner.translate(0, -(rect.size.height + 2 * rect.origin.y));
 }
 
-struct GlyphsAndAdvancesStorage {
-    Vector<GlyphBufferGlyph> glyphs;
-    Vector<GlyphBufferAdvance> advances;
-};
+void DrawGlyphsRecorder::drawOTSVGRun(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
+{
+    FloatPoint penPosition = startPoint;
+    for (unsigned i = 0; i < numGlyphs; ++i) {
+        auto bounds = font.boundsForGlyph(glyphs[i]);
+        if (auto imageBufferDescription = ImageBuffer::createCompatibleBuffer(bounds, m_owner)) {
+            FontCascade::drawGlyphs(imageBufferDescription->imageBuffer->context(), font, glyphs + i, advances + i, 1, FloatPoint(), smoothingMode);
+            auto destinationRect = imageBufferDescription->inflatedRectInUserCoordinates;
+            destinationRect.moveBy(penPosition);
+            m_owner.drawImageBuffer(imageBufferDescription->imageBuffer, destinationRect);
+        }
+        penPosition.move(size(advances[i]));
+    }
+}
 
-struct GlyphsAndAdvances {
-    const GlyphBufferGlyph* glyphs;
-    const GlyphBufferAdvance* advances;
-    unsigned numGlyphs;
-    GlyphBufferAdvance initialAdvance;
-    std::optional<GlyphsAndAdvancesStorage> storage;
-};
+void DrawGlyphsRecorder::drawNonOTSVGRun(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
+{
+    prepareInternalContext(font, smoothingMode);
+    FontCascade::drawGlyphs(m_internalContext, font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
+    concludeInternalContext();
+}
 
-static GlyphsAndAdvances filterOutOTSVGGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs)
+void DrawGlyphsRecorder::drawBySplittingIntoOTSVGAndNonOTSVGRuns(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
 {
     auto otsvgGlyphs = font.findOTSVGGlyphs(glyphs, numGlyphs);
-    if (!otsvgGlyphs)
-        return { glyphs, advances, numGlyphs, makeGlyphBufferAdvance(), { }};
+    if (!otsvgGlyphs) {
+        drawNonOTSVGRun(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
+        return;
+    }
 
     ASSERT(otsvgGlyphs->size() >= numGlyphs);
 
-    GlyphsAndAdvances result;
-    result.initialAdvance = makeGlyphBufferAdvance();
-    result.storage = GlyphsAndAdvancesStorage();
-
-    result.storage->glyphs.reserveInitialCapacity(numGlyphs);
-    result.storage->advances.reserveInitialCapacity(numGlyphs);
-
-    for (unsigned i = 0; i < numGlyphs; ++i) {
-        ASSERT(result.storage->glyphs.size() == result.storage->advances.size());
-        if (otsvgGlyphs->quickGet(i)) {
-            if (result.storage->advances.isEmpty())
-                result.initialAdvance = makeGlyphBufferAdvance(size(result.initialAdvance) + size(advances[i]));
-            else
-                result.storage->advances.last() = makeGlyphBufferAdvance(size(result.storage->advances.last()) + size(advances[i]));
-        } else {
-            result.storage->glyphs.uncheckedAppend(glyphs[i]);
-            result.storage->advances.uncheckedAppend(advances[i]);
+    // We can't just partition the glyphs into OT-SVG glyphs and non-OT-SVG glyphs because glyphs are allowed to draw outside of their layout boxes.
+    // This means that glyphs can overlap, which means we have to get the z-order correct. We can't have an earlier run be drawn on top of a later run.
+    FloatPoint runOrigin = startPoint;
+    FloatPoint penPosition = startPoint;
+    size_t glyphCountInRun = 0;
+    bool isOTSVGRun = false;
+    unsigned i;
+    auto draw = [&] () {
+        if (!glyphCountInRun)
+            return;
+        if (isOTSVGRun)
+            drawOTSVGRun(font, glyphs + i - glyphCountInRun, advances + i - glyphCountInRun, glyphCountInRun, runOrigin, smoothingMode);
+        else
+            drawNonOTSVGRun(font, glyphs + i - glyphCountInRun, advances + i - glyphCountInRun, glyphCountInRun, runOrigin, smoothingMode);
+    };
+    for (i = 0; i < numGlyphs; ++i) {
+        bool isOTSVGGlyph = otsvgGlyphs->quickGet(i);
+        if (isOTSVGGlyph != isOTSVGRun) {
+            draw();
+            isOTSVGRun = isOTSVGGlyph;
+            glyphCountInRun = 0;
+            runOrigin = penPosition;
         }
-        ASSERT(result.storage->glyphs.size() == result.storage->advances.size());
+        ++glyphCountInRun;
+        penPosition.move(size(advances[i]));
     }
-
-    result.glyphs = result.storage->glyphs.data();
-    result.advances = result.storage->advances.data();
-    result.numGlyphs = result.storage->glyphs.size();
-
-    return result;
+    draw();
 }
 
 void DrawGlyphsRecorder::drawGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
@@ -428,14 +441,7 @@
 
     ASSERT(m_deconstructDrawGlyphs == DeconstructDrawGlyphs::Yes);
 
-    // FIXME: <rdar://problem/70166552> Record OTSVG glyphs.
-    GlyphsAndAdvances glyphsAndAdvancesWithoutOTSVGGlyphs = filterOutOTSVGGlyphs(font, glyphs, advances, numGlyphs);
-    ASSERT(glyphsAndAdvancesWithoutOTSVGGlyphs.glyphs == glyphs || glyphsAndAdvancesWithoutOTSVGGlyphs.glyphs == glyphsAndAdvancesWithoutOTSVGGlyphs.storage->glyphs.data());
-    ASSERT(glyphsAndAdvancesWithoutOTSVGGlyphs.advances == advances || glyphsAndAdvancesWithoutOTSVGGlyphs.advances == glyphsAndAdvancesWithoutOTSVGGlyphs.storage->advances.data());
-
-    prepareInternalContext(font, smoothingMode);
-    FontCascade::drawGlyphs(m_internalContext, font, glyphsAndAdvancesWithoutOTSVGGlyphs.glyphs, glyphsAndAdvancesWithoutOTSVGGlyphs.advances, glyphsAndAdvancesWithoutOTSVGGlyphs.numGlyphs, startPoint + size(glyphsAndAdvancesWithoutOTSVGGlyphs.initialAdvance), smoothingMode);
-    concludeInternalContext();
+    drawBySplittingIntoOTSVGAndNonOTSVGRuns(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
 }
 
 void DrawGlyphsRecorder::drawNativeText(CTFontRef font, CGFloat fontSize, CTLineRef line, CGRect lineRect)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to