[LFC][Painting] Add support for image/background image painting
https://bugs.webkit.org/show_bug.cgi?id=203054
<rdar://problem/56347733>

Reviewed by Antti Koivisto.

In addition to image rendering, this patch also addresses various run/decoration painting issues.

* layout/displaytree/DisplayPainter.cpp:
(WebCore::Display::paintBoxDecoration):
(WebCore::Display::paintInlineContent):
(WebCore::Display::absoluteDisplayBox):
(WebCore::Display::paintBoxDecorationAndChildren):
(WebCore::Display::Painter::paint):
* layout/displaytree/DisplayRun.h:
(WebCore::Display::Run::setImage):
(WebCore::Display::Run::image const):
* layout/inlineformatting/InlineLine.cpp:
(WebCore::Layout::Line::appendNonReplacedInlineBox):
(WebCore::Layout::Line::appendReplacedInlineBox):
* layout/layouttree/LayoutReplaced.h:
(WebCore::Layout::Replaced::setCachedImage):
(WebCore::Layout::Replaced::cachedImage const):
* layout/layouttree/LayoutTreeBuilder.cpp:
(WebCore::Layout::TreeBuilder::createLayoutBox):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251239 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 42f50db..5fcff80 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,5 +1,33 @@
 2019-10-17  Zalan Bujtas  <zalan@apple.com>
 
+        [LFC][Painting] Add support for image/background image painting
+        https://bugs.webkit.org/show_bug.cgi?id=203054
+        <rdar://problem/56347733>
+
+        Reviewed by Antti Koivisto.
+
+        In addition to image rendering, this patch also addresses various run/decoration painting issues.
+
+        * layout/displaytree/DisplayPainter.cpp:
+        (WebCore::Display::paintBoxDecoration):
+        (WebCore::Display::paintInlineContent):
+        (WebCore::Display::absoluteDisplayBox):
+        (WebCore::Display::paintBoxDecorationAndChildren):
+        (WebCore::Display::Painter::paint):
+        * layout/displaytree/DisplayRun.h:
+        (WebCore::Display::Run::setImage):
+        (WebCore::Display::Run::image const):
+        * layout/inlineformatting/InlineLine.cpp:
+        (WebCore::Layout::Line::appendNonReplacedInlineBox):
+        (WebCore::Layout::Line::appendReplacedInlineBox):
+        * layout/layouttree/LayoutReplaced.h:
+        (WebCore::Layout::Replaced::setCachedImage):
+        (WebCore::Layout::Replaced::cachedImage const):
+        * layout/layouttree/LayoutTreeBuilder.cpp:
+        (WebCore::Layout::TreeBuilder::createLayoutBox):
+
+2019-10-17  Zalan Bujtas  <zalan@apple.com>
+
         [LFC][IFC] Add Display::Run/LineBox mapping
         https://bugs.webkit.org/show_bug.cgi?id=203051
         <rdar://problem/56342487>
diff --git a/Source/WebCore/layout/displaytree/DisplayPainter.cpp b/Source/WebCore/layout/displaytree/DisplayPainter.cpp
index 4ad95c6..d78a8f2 100644
--- a/Source/WebCore/layout/displaytree/DisplayPainter.cpp
+++ b/Source/WebCore/layout/displaytree/DisplayPainter.cpp
@@ -28,6 +28,7 @@
 
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
 
+#include "CachedImage.h"
 #include "Color.h"
 #include "DisplayBox.h"
 #include "GraphicsContext.h"
@@ -42,17 +43,28 @@
 namespace WebCore {
 namespace Display {
 
-static void paintBoxDecoration(GraphicsContext& context, const Box& absoluteDisplayBox, const RenderStyle& style)
+static void paintBoxDecoration(GraphicsContext& context, const Box& absoluteDisplayBox, const RenderStyle& style, bool needsMarginPainting)
 {
-    auto borderBoxAbsoluteTopLeft = absoluteDisplayBox.topLeft();
+    auto decorationBoxTopLeft = needsMarginPainting ? absoluteDisplayBox.rectWithMargin().topLeft() : absoluteDisplayBox.topLeft();
+    auto decorationBoxSize = needsMarginPainting ? absoluteDisplayBox.rectWithMargin().size() : LayoutSize(absoluteDisplayBox.borderBoxWidth(), absoluteDisplayBox.borderBoxHeight());
     // Background color
-    if (style.hasBackground())
-        context.fillRect({ borderBoxAbsoluteTopLeft, FloatSize { absoluteDisplayBox.borderBoxWidth(), absoluteDisplayBox.borderBoxHeight() } }, style.backgroundColor());
+    if (style.hasBackground()) {
+        context.fillRect({ decorationBoxTopLeft, decorationBoxSize }, style.backgroundColor());
+        if (style.hasBackgroundImage()) {
+            auto& backgroundLayer = style.backgroundLayers();
+            if (backgroundLayer.image() && backgroundLayer.image()->cachedImage() && backgroundLayer.image()->cachedImage()->image())
+                context.drawImage(*backgroundLayer.image()->cachedImage()->image(), { decorationBoxTopLeft, backgroundLayer.image()->cachedImage()->image()->size() });
+        }
+    }
 
     // Border
-    if (style.hasBorder()) {
+    if (style.hasVisibleBorder()) {
 
         auto drawBorderSide = [&](auto start, auto end, const auto& borderStyle) {
+            if (!borderStyle.width())
+                return;
+            if (borderStyle.style() == BorderStyle::None || borderStyle.style() == BorderStyle::Hidden)
+                return;
             context.setStrokeColor(borderStyle.color());
             context.setStrokeThickness(borderStyle.width());
             context.drawLine(start, end);
@@ -60,38 +72,38 @@
 
         context.setFillColor(Color::transparent);
 
-        auto borderBoxWidth = absoluteDisplayBox.borderBoxWidth();
-        auto borderBoxHeight = absoluteDisplayBox.borderBoxHeight();
+        auto decorationBoxWidth = decorationBoxSize.width();
+        auto decorationBoxHeight = decorationBoxSize.height();
 
         // Top
         {
             auto borderWidth = style.borderTop().width();
-            auto start = LayoutPoint { borderBoxAbsoluteTopLeft };
-            auto end = LayoutPoint { start.x() + borderBoxWidth, start.y() + borderWidth };
+            auto start = LayoutPoint { decorationBoxTopLeft };
+            auto end = LayoutPoint { start.x() + decorationBoxWidth, start.y() + borderWidth };
             drawBorderSide(start, end, style.borderTop());
         }
 
         // Right
         {
             auto borderWidth = style.borderRight().width();
-            auto start = LayoutPoint { borderBoxAbsoluteTopLeft.x() + borderBoxWidth - borderWidth, borderBoxAbsoluteTopLeft.y() };
-            auto end = LayoutPoint { start.x() + borderWidth, borderBoxAbsoluteTopLeft.y() + borderBoxHeight };
+            auto start = LayoutPoint { decorationBoxTopLeft.x() + decorationBoxWidth - borderWidth, decorationBoxTopLeft.y() };
+            auto end = LayoutPoint { start.x() + borderWidth, decorationBoxTopLeft.y() + decorationBoxHeight };
             drawBorderSide(start, end, style.borderRight());
         }
 
         // Bottom
         {
             auto borderWidth = style.borderBottom().width();
-            auto start = LayoutPoint { borderBoxAbsoluteTopLeft.x(), borderBoxAbsoluteTopLeft.y() + borderBoxHeight - borderWidth };
-            auto end = LayoutPoint { start.x() + borderBoxWidth, start.y() + borderWidth };
+            auto start = LayoutPoint { decorationBoxTopLeft.x(), decorationBoxTopLeft.y() + decorationBoxHeight - borderWidth };
+            auto end = LayoutPoint { start.x() + decorationBoxWidth, start.y() + borderWidth };
             drawBorderSide(start, end, style.borderBottom());
         }
 
         // Left
         {
             auto borderWidth = style.borderLeft().width();
-            auto start = borderBoxAbsoluteTopLeft;
-            auto end = LayoutPoint { start.x() + borderWidth, borderBoxAbsoluteTopLeft.y() + borderBoxHeight };
+            auto start = decorationBoxTopLeft;
+            auto end = LayoutPoint { start.x() + borderWidth, decorationBoxTopLeft.y() + decorationBoxHeight };
             drawBorderSide(start, end, style.borderLeft());
         }
     }
@@ -102,64 +114,69 @@
     auto& inlineRuns = formattingState.inlineRuns();
     if (inlineRuns.isEmpty())
         return;
-    // FIXME: We should be able to paint runs independently from inline items.
-    unsigned runIndex = 0;
-    for (auto& inlineItem : formattingState.inlineItems()) {
-        auto& style = inlineItem->style();
-        context.setStrokeColor(style.color());
-        context.setFillColor(style.color());
 
-        if (inlineItem->isText()) {
-            auto& inlineTextItem = downcast<Layout::InlineTextItem>(*inlineItem);
-            auto inlineContent = inlineTextItem.layoutBox().textContent();
-            while (true) {
-                auto& run = *inlineRuns[runIndex++];
-                auto textContext = run.textContext().value();
-                auto runContent = inlineContent.substring(textContext.start(), textContext.length());
-                auto logicalTopLeft = rootAbsoluteDisplayBox.topLeft() + run.logicalTopLeft();
-                context.drawText(style.fontCascade(), TextRun { runContent }, { logicalTopLeft.x(), logicalTopLeft.y() + formattingState.lineBoxes()[0]->baselineOffset() });
-                if (inlineTextItem.end() == textContext.end())
-                    break;
-                if (runIndex == inlineRuns.size())
-                    return;
-            }
-            continue;
-        }
+    for (auto& run : inlineRuns) {
+        if (run->textContext()) {
+            auto& style = run->style();
+            context.setStrokeColor(style.color());
+            context.setFillColor(style.color());
 
-        if (inlineItem->isBox()) {
-            auto& run = *inlineRuns[runIndex++];
-            auto logicalTopLeft = rootAbsoluteDisplayBox.topLeft() + run.logicalTopLeft();
-            context.fillRect({ logicalTopLeft, FloatSize { run.logicalWidth(), run.logicalHeight() } });
-            continue;
+            auto logicalLeft = rootAbsoluteDisplayBox.left() + run->logicalLeft();
+            // FIXME: Add non-baseline align painting
+            auto& lineBox = formattingState.lineBoxForRun(*run);
+            auto baselineOffset = rootAbsoluteDisplayBox.top() + lineBox.logicalTop() + lineBox.baselineOffset(); 
+            context.drawText(style.fontCascade(), TextRun { run->textContext()->content() }, { logicalLeft, baselineOffset });
+        } else if (auto* cachedImage = run->image()) {
+            auto runAbsoluteRect = FloatRect { rootAbsoluteDisplayBox.left() + run->logicalLeft(), rootAbsoluteDisplayBox.top() + run->logicalTop(), run->logicalWidth(), run->logicalHeight() };
+            context.drawImage(*cachedImage->image(), runAbsoluteRect);
         }
     }
 }
 
+static Box absoluteDisplayBox(const Layout::LayoutState& layoutState, const Layout::Box& layoutBox)
+{
+    // Should never really happen but table code is way too incomplete.
+    if (!layoutState.hasDisplayBox(layoutBox))
+        return { };
+
+    auto absoluteBox = Box { layoutState.displayBoxForLayoutBox(layoutBox) };
+    for (auto* container = layoutBox.containingBlock(); container != &layoutBox.initialContainingBlock(); container = container->containingBlock())
+        absoluteBox.moveBy(layoutState.displayBoxForLayoutBox(*container).topLeft());
+    return absoluteBox;
+}
+
+static void paintBoxDecorationAndChildren(GraphicsContext& context, const Layout::LayoutState& layoutState, const Layout::Box& layoutBox)
+{
+    if (!layoutBox.isAnonymous())
+        paintBoxDecoration(context, absoluteDisplayBox(layoutState, layoutBox), layoutBox.style(), layoutBox.isBodyBox());
+
+    if (!is<Layout::Container>(layoutBox))
+        return;
+    for (auto& childLayoutBox : Layout::childrenOfType<Layout::Box>(downcast<Layout::Container>(layoutBox))) {
+        if (childLayoutBox.style().visibility() != Visibility::Visible)
+            continue;
+        paintBoxDecorationAndChildren(context, layoutState, childLayoutBox);
+    }
+}
+
 void Painter::paint(const Layout::LayoutState& layoutState, GraphicsContext& context)
 {
-    auto absoluteDisplayBox = [&](const auto& layoutBox) {
-        auto absoluteBox = Box { layoutState.displayBoxForLayoutBox(layoutBox) };
-        for (auto* container = layoutBox.containingBlock(); container != &layoutBox.initialContainingBlock(); container = container->containingBlock())
-            absoluteBox.moveBy(layoutState.displayBoxForLayoutBox(*container).topLeft());
-        return absoluteBox;
-    };
-
     auto& layoutRoot = layoutState.root();
     auto& rootDisplayBox = layoutState.displayBoxForLayoutBox(layoutRoot);
     context.fillRect({ FloatPoint { }, FloatSize { rootDisplayBox.borderBoxWidth(), rootDisplayBox.borderBoxHeight() } }, Color::white);
+    if (!layoutRoot.firstChild())
+        return;
 
     // 1. Paint box decoration (both block and inline).
-    for (auto& layoutBox : Layout::descendantsOfType<Layout::Box>(layoutRoot)) {
-        if (layoutBox.isAnonymous())
-            continue;
-        paintBoxDecoration(context, absoluteDisplayBox(layoutBox), layoutBox.style());
-    }
+    paintBoxDecorationAndChildren(context, layoutState, *layoutRoot.firstChild());
 
     // 2. Paint content
     for (auto& layoutBox : Layout::descendantsOfType<Layout::Box>(layoutRoot)) {
+        if (layoutBox.style().visibility() != Visibility::Visible)
+            continue;
         if (layoutBox.establishesInlineFormattingContext()) {
             auto& container = downcast<Layout::Container>(layoutBox);
-            paintInlineContent(context, absoluteDisplayBox(container), downcast<Layout::InlineFormattingState>(layoutState.establishedFormattingState(container)));
+            paintInlineContent(context, absoluteDisplayBox(layoutState, container), downcast<Layout::InlineFormattingState>(layoutState.establishedFormattingState(container)));
             continue;
         }
     }
diff --git a/Source/WebCore/layout/displaytree/DisplayRun.h b/Source/WebCore/layout/displaytree/DisplayRun.h
index 58a46af..e107c58 100644
--- a/Source/WebCore/layout/displaytree/DisplayRun.h
+++ b/Source/WebCore/layout/displaytree/DisplayRun.h
@@ -32,6 +32,9 @@
 #include "RenderStyle.h"
 
 namespace WebCore {
+
+class CachedImage;
+
 namespace Display {
 
 struct Run {
@@ -81,11 +84,15 @@
     Optional<TextContext>& textContext() { return m_textContext; }
     Optional<TextContext> textContext() const { return m_textContext; }
 
+    void setImage(CachedImage& image) { m_cachedImage = &image; }
+    CachedImage* image() const { return m_cachedImage; }
+
     const RenderStyle& style() const { return m_style; }
 
 private:
     // FIXME: Find out the Display::Run <-> paint style setup.
     const RenderStyle& m_style;
+    CachedImage* m_cachedImage { nullptr };
     Rect m_logicalRect;
     Optional<TextContext> m_textContext;
 };
diff --git a/Source/WebCore/layout/inlineformatting/InlineLine.cpp b/Source/WebCore/layout/inlineformatting/InlineLine.cpp
index 62c327d..ad89be9 100644
--- a/Source/WebCore/layout/inlineformatting/InlineLine.cpp
+++ b/Source/WebCore/layout/inlineformatting/InlineLine.cpp
@@ -378,7 +378,7 @@
 void Line::appendNonReplacedInlineBox(const InlineItem& inlineItem, LayoutUnit logicalWidth)
 {
     auto& boxGeometry = formattingContext().geometryForBox(inlineItem.layoutBox());
-    auto horizontalMargin = boxGeometry.horizontalMargin();    
+    auto horizontalMargin = boxGeometry.horizontalMargin();
     auto logicalRect = Display::Rect { };
 
     logicalRect.setLeft(contentLogicalWidth() + horizontalMargin.start);
@@ -396,8 +396,11 @@
 
 void Line::appendReplacedInlineBox(const InlineItem& inlineItem, LayoutUnit logicalWidth)
 {
-    // FIXME Surely replaced boxes behave differently.
+    ASSERT(inlineItem.layoutBox().isReplaced());
+    // FIXME: Surely replaced boxes behave differently.
     appendNonReplacedInlineBox(inlineItem, logicalWidth);
+    if (auto* replaced = inlineItem.layoutBox().replaced(); replaced && replaced->cachedImage())
+        m_runList.last()->m_displayRun.setImage(*replaced->cachedImage());
 }
 
 void Line::appendHardLineBreak(const InlineItem& inlineItem)
diff --git a/Source/WebCore/layout/layouttree/LayoutReplaced.h b/Source/WebCore/layout/layouttree/LayoutReplaced.h
index a1311ad..13f93a6 100644
--- a/Source/WebCore/layout/layouttree/LayoutReplaced.h
+++ b/Source/WebCore/layout/layouttree/LayoutReplaced.h
@@ -27,6 +27,7 @@
 
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
 
+#include "CachedImage.h"
 #include "LayoutSize.h"
 #include "LayoutUnit.h"
 #include <wtf/IsoMalloc.h>
@@ -44,6 +45,9 @@
     Replaced(const Box&);
     ~Replaced() = default;
 
+    void setCachedImage(CachedImage& cachedImage) { m_cachedImage = &cachedImage; }
+    CachedImage* cachedImage() const { return m_cachedImage; }
+
     // FIXME: Temporary until after intrinsic size change is tracked internallys.
     void setIntrinsicSize(LayoutSize size) { m_intrinsicSize = size; }
     void setIntrinsicRatio(LayoutUnit ratio) { m_intrinsicRatio = ratio; };
@@ -61,6 +65,7 @@
     WeakPtr<const Box> m_layoutBox;
     Optional<LayoutSize> m_intrinsicSize;
     Optional<LayoutUnit> m_intrinsicRatio;
+    CachedImage* m_cachedImage { nullptr };
 };
 
 }
diff --git a/Source/WebCore/layout/layouttree/LayoutTreeBuilder.cpp b/Source/WebCore/layout/layouttree/LayoutTreeBuilder.cpp
index e5b588b..1848bc7 100644
--- a/Source/WebCore/layout/layouttree/LayoutTreeBuilder.cpp
+++ b/Source/WebCore/layout/layouttree/LayoutTreeBuilder.cpp
@@ -28,6 +28,7 @@
 
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
 
+#include "CachedImage.h"
 #include "DisplayBox.h"
 #include "DisplayRun.h"
 #include "HTMLTableCellElement.h"
@@ -166,6 +167,8 @@
             auto& imageRenderer = downcast<RenderImage>(renderer);
             if (imageRenderer.shouldDisplayBrokenImageIcon())
                 childLayoutBox->replaced()->setIntrinsicRatio(1);
+            if (imageRenderer.cachedImage())
+                childLayoutBox->replaced()->setCachedImage(*imageRenderer.cachedImage());
         }
     } else {
         if (displayType == DisplayType::Block) {