Implement InlineTextBox painting using marker subranges
authordbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 Dec 2017 21:48:19 +0000 (21:48 +0000)
committerdbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 Dec 2017 21:48:19 +0000 (21:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=180984
<rdar://problem/36139364>

Reviewed by David Hyatt.

Source/WebCore:

As a step towards implementing CSS Pseudo-Elements Module Level 4, explicitly decompose a
text line into subrange objects that may be styled (say, to give the appearance of selected
text) and painted.

No functionality changed. So, no new tests.

* rendering/InlineTextBox.cpp:
(WebCore::InlineTextBox::localSelectionRect const): Update comments.

(WebCore::InlineTextBox::MarkerSubrangeStyle::operator== const):
(WebCore::InlineTextBox::MarkerSubrangeStyle::operator!= const):
(WebCore::InlineTextBox::StyledMarkerSubrange::StyledMarkerSubrange):
Define a subclass to represent a marker subrange that has associated style information.
We will make use of the style information when painting the subrange.

(WebCore::createMarkerSubrangeFromSelectionInBox): Convenience function to instantiate a
MarkerSubrange instance from the current selection of a specified text box.

(WebCore::InlineTextBox::paint): Write in terms of MarkerSubrange. We keep composition backgrounds
and composition underlines as special cases for now. We represent all other subranges of the line
as MarkerSubrange instances, including unmarked text (i.e. non-selected text without an associated
document marker). The subranges for the gaps between document markers and selection are implicitly
created by subdividing the entire line. Support for PaintBehaviorExcludeSelection is implemented
by subdividing the entire line with the selection subrange and then removing the selection subrange
from the resulting list of subdivisions before painting with the resulting list.

(WebCore::InlineTextBox::paintPlatformDocumentMarkers): Renamed; formerly named paintDocumentMarkers.
(WebCore::InlineTextBox::paintPlatformDocumentMarker): Renamed; formerly named paintDocumentMarker.

(WebCore::InlineTextBox::computeStyleForUnmarkedMarkerSubrange const): Convenience function to
compute the style for unmarked text.

(WebCore::InlineTextBox::resolveStyleForSubrange): Added

(WebCore::InlineTextBox::subdivideAndResolveStyle): Subdivides the list of marker subranges and
coalesces subranges of the same type (e.g. selection) or with the same style. The latter strategy
preserves the optimization of drawing the text of the entire line in one draw command when we know
that the selected text looks identical to non-selected text. This optimization was performed in TextPainter::paint().

(WebCore::InlineTextBox::collectSubrangesForDraggedContent): Added.
(WebCore::InlineTextBox::collectSubrangesForDocumentMarkers): Added.
(WebCore::InlineTextBox::textOriginFromBoxRect const): Added.

(WebCore::InlineTextBox::paintMarkerSubranges):
(WebCore::InlineTextBox::paintTextSubrangeBackground):
(WebCore::InlineTextBox::paintTextSubrangeForeground):
(WebCore::InlineTextBox::paintTextSubrangeDecoration):
Paint the marker subrange.

(WebCore::InlineTextBox::paintCompositionBackground): Moved function to group it with the logic to
paint composition underlines. Modified to pass clamped offsets to paintTextSubrangeBackground() as
paintTextSubrangeBackground() now expects them.

(WebCore::mirrorRTLSegment): Moved function to be above paintCompositionUnderline() as it is the only
function that makes use of it.

(WebCore::InlineTextBox::paintSelection): Deleted.
(WebCore::InlineTextBox::paintTextMatchMarker): Deleted.
(WebCore::InlineTextBox::paintDecoration): Deleted.
(WebCore::InlineTextBox::paintDocumentMarker): Deleted; renamed to paintPlatformDocumentMarker.
(WebCore::InlineTextBox::paintDocumentMarkers): Deleted; renamed to paintPlatformDocumentMarkers.
* rendering/InlineTextBox.h:
(WebCore::InlineTextBox::paintMarkerSubranges): Added.
* rendering/MarkerSubrange.h:
(WebCore::MarkerSubrange::isEmpty const): Added.
(WebCore::MarkerSubrange::operator!= const): Added.
(WebCore::MarkerSubrange::operator== const): Added.
* rendering/SimpleLineLayoutFunctions.cpp:
(WebCore::SimpleLineLayout::paintFlow): Updated code as we no longer need to pass the text run length
to TextPainter::paint(). Also modernize the code while I am here.
* rendering/TextDecorationPainter.cpp:
(WebCore::TextDecorationPainter::Styles::operator== const): Added.
(WebCore::TextDecorationPainter::TextDecorationPainter): Modified to take an optional TextDecorationPainter::Styles
instance as an argument and removed the unused parameter for PseudoId.
* rendering/TextDecorationPainter.h:
(WebCore::TextDecorationPainter::Styles::operator!= const): Added.
* rendering/TextPaintStyle.cpp:
(WebCore::TextPaintStyle::operator== const): Added.
(WebCore::computeTextSelectionPaintStyle): Removed the now unused out-arguments paintSelectedTextOnly, paintSelectedTextSeparately,
and paintNonSelectedTextOnly. The values stored in these out-arguments were intended to be used by the caller to
minimize the number of drawing commands. The value of paintSelectedTextSeparately indicated whether selected text
would look identical to non-selected text so that a caller could use one paint command to draw the entire line
instead of issuing two or more drawing commands to paint non-selected and selected text separately. We now
accomplish the same optimization by coalescing subrange styles in InlineTextBox::subdivideAndResolveStyle().
The value of paintNonSelectedTextOnly, as its name states, indicated whether a caller should only paint non-selected
text and hence skip painting of selected text. This value was only used when painting dragged content (i.e. DocumentMarker::DraggedContent
markers) and its effect is now more directly achieved by the dragged content logic in InlineTextBox::paint().
* rendering/TextPaintStyle.h:
(WebCore::TextPaintStyle::operator!= const): Added.
* rendering/TextPainter.cpp:
(WebCore::TextPainter::paint): Implemented in terms TextPainter::paintRange() now that InlineTextBox::paint() optimizes
for the number of drawing calls. See remarks for WebCore::computeTextSelectionPaintStyle() for more details.
* rendering/TextPainter.h: Group together related member functions.
(WebCore::TextPainter::setSelectionStyle): Deleted.
(WebCore::TextPainter::setSelectionShadow): Deleted.

Tools:

Update unit tests now that we use subranges for the painting of dragged content.

* TestWebKitAPI/Tests/WebCore/MarkerSubrange.cpp:
(WebCore::operator<<):
(WebCore::operator==): Deleted; moved to class MarkerSubrange.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@226138 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
Source/WebCore/ChangeLog
Source/WebCore/rendering/InlineTextBox.cpp
Source/WebCore/rendering/InlineTextBox.h
Source/WebCore/rendering/MarkerSubrange.h
Source/WebCore/rendering/SimpleLineLayoutFunctions.cpp
Source/WebCore/rendering/TextDecorationPainter.cpp
Source/WebCore/rendering/TextDecorationPainter.h
Source/WebCore/rendering/TextPaintStyle.cpp
Source/WebCore/rendering/TextPaintStyle.h
Source/WebCore/rendering/TextPainter.cpp
Source/WebCore/rendering/TextPainter.h
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebCore/MarkerSubrange.cpp

index 40c4f17f4bdeb6f18093c522b06410461cab1f99..7373ede7c3f728eb64b6fab372ae48f1fb725fae 100644 (file)
@@ -1,3 +1,107 @@
+2017-12-19  Daniel Bates  <dabates@apple.com>
+
+        Implement InlineTextBox painting using marker subranges
+        https://bugs.webkit.org/show_bug.cgi?id=180984
+        <rdar://problem/36139364>
+
+        Reviewed by David Hyatt.
+
+        As a step towards implementing CSS Pseudo-Elements Module Level 4, explicitly decompose a
+        text line into subrange objects that may be styled (say, to give the appearance of selected
+        text) and painted.
+
+        No functionality changed. So, no new tests.
+
+        * rendering/InlineTextBox.cpp:
+        (WebCore::InlineTextBox::localSelectionRect const): Update comments.
+
+        (WebCore::InlineTextBox::MarkerSubrangeStyle::operator== const):
+        (WebCore::InlineTextBox::MarkerSubrangeStyle::operator!= const):
+        (WebCore::InlineTextBox::StyledMarkerSubrange::StyledMarkerSubrange):
+        Define a subclass to represent a marker subrange that has associated style information.
+        We will make use of the style information when painting the subrange.
+
+        (WebCore::createMarkerSubrangeFromSelectionInBox): Convenience function to instantiate a
+        MarkerSubrange instance from the current selection of a specified text box.
+
+        (WebCore::InlineTextBox::paint): Write in terms of MarkerSubrange. We keep composition backgrounds
+        and composition underlines as special cases for now. We represent all other subranges of the line
+        as MarkerSubrange instances, including unmarked text (i.e. non-selected text without an associated
+        document marker). The subranges for the gaps between document markers and selection are implicitly
+        created by subdividing the entire line. Support for PaintBehaviorExcludeSelection is implemented
+        by subdividing the entire line with the selection subrange and then removing the selection subrange
+        from the resulting list of subdivisions before painting with the resulting list.
+
+        (WebCore::InlineTextBox::paintPlatformDocumentMarkers): Renamed; formerly named paintDocumentMarkers.
+        (WebCore::InlineTextBox::paintPlatformDocumentMarker): Renamed; formerly named paintDocumentMarker.
+
+        (WebCore::InlineTextBox::computeStyleForUnmarkedMarkerSubrange const): Convenience function to
+        compute the style for unmarked text.
+
+        (WebCore::InlineTextBox::resolveStyleForSubrange): Added
+
+        (WebCore::InlineTextBox::subdivideAndResolveStyle): Subdivides the list of marker subranges and
+        coalesces subranges of the same type (e.g. selection) or with the same style. The latter strategy
+        preserves the optimization of drawing the text of the entire line in one draw command when we know
+        that the selected text looks identical to non-selected text. This optimization was performed in TextPainter::paint().
+
+        (WebCore::InlineTextBox::collectSubrangesForDraggedContent): Added.
+        (WebCore::InlineTextBox::collectSubrangesForDocumentMarkers): Added.
+        (WebCore::InlineTextBox::textOriginFromBoxRect const): Added.
+
+        (WebCore::InlineTextBox::paintMarkerSubranges):
+        (WebCore::InlineTextBox::paintTextSubrangeBackground):
+        (WebCore::InlineTextBox::paintTextSubrangeForeground):
+        (WebCore::InlineTextBox::paintTextSubrangeDecoration):
+        Paint the marker subrange.
+
+        (WebCore::InlineTextBox::paintCompositionBackground): Moved function to group it with the logic to
+        paint composition underlines. Modified to pass clamped offsets to paintTextSubrangeBackground() as
+        paintTextSubrangeBackground() now expects them.
+
+        (WebCore::mirrorRTLSegment): Moved function to be above paintCompositionUnderline() as it is the only
+        function that makes use of it.
+
+        (WebCore::InlineTextBox::paintSelection): Deleted.
+        (WebCore::InlineTextBox::paintTextMatchMarker): Deleted.
+        (WebCore::InlineTextBox::paintDecoration): Deleted.
+        (WebCore::InlineTextBox::paintDocumentMarker): Deleted; renamed to paintPlatformDocumentMarker.
+        (WebCore::InlineTextBox::paintDocumentMarkers): Deleted; renamed to paintPlatformDocumentMarkers.
+        * rendering/InlineTextBox.h:
+        (WebCore::InlineTextBox::paintMarkerSubranges): Added.
+        * rendering/MarkerSubrange.h:
+        (WebCore::MarkerSubrange::isEmpty const): Added.
+        (WebCore::MarkerSubrange::operator!= const): Added.
+        (WebCore::MarkerSubrange::operator== const): Added.
+        * rendering/SimpleLineLayoutFunctions.cpp:
+        (WebCore::SimpleLineLayout::paintFlow): Updated code as we no longer need to pass the text run length
+        to TextPainter::paint(). Also modernize the code while I am here.
+        * rendering/TextDecorationPainter.cpp:
+        (WebCore::TextDecorationPainter::Styles::operator== const): Added.
+        (WebCore::TextDecorationPainter::TextDecorationPainter): Modified to take an optional TextDecorationPainter::Styles
+        instance as an argument and removed the unused parameter for PseudoId.
+        * rendering/TextDecorationPainter.h:
+        (WebCore::TextDecorationPainter::Styles::operator!= const): Added.
+        * rendering/TextPaintStyle.cpp:
+        (WebCore::TextPaintStyle::operator== const): Added.
+        (WebCore::computeTextSelectionPaintStyle): Removed the now unused out-arguments paintSelectedTextOnly, paintSelectedTextSeparately,
+        and paintNonSelectedTextOnly. The values stored in these out-arguments were intended to be used by the caller to
+        minimize the number of drawing commands. The value of paintSelectedTextSeparately indicated whether selected text
+        would look identical to non-selected text so that a caller could use one paint command to draw the entire line
+        instead of issuing two or more drawing commands to paint non-selected and selected text separately. We now
+        accomplish the same optimization by coalescing subrange styles in InlineTextBox::subdivideAndResolveStyle().
+        The value of paintNonSelectedTextOnly, as its name states, indicated whether a caller should only paint non-selected
+        text and hence skip painting of selected text. This value was only used when painting dragged content (i.e. DocumentMarker::DraggedContent
+        markers) and its effect is now more directly achieved by the dragged content logic in InlineTextBox::paint().
+        * rendering/TextPaintStyle.h:
+        (WebCore::TextPaintStyle::operator!= const): Added.
+        * rendering/TextPainter.cpp:
+        (WebCore::TextPainter::paint): Implemented in terms TextPainter::paintRange() now that InlineTextBox::paint() optimizes
+        for the number of drawing calls. See remarks for WebCore::computeTextSelectionPaintStyle() for more details.
+        * rendering/TextPainter.h: Group together related member functions.
+        (WebCore::TextPainter::setSelectionStyle): Deleted.
+        (WebCore::TextPainter::setSelectionShadow): Deleted.
+
 2017-12-19  Daniel Bates  <dabates@apple.com>
 
         Add support for computing the frontmost longest effective marker subrange
index fa7e0ccd9348ab7e83d3358c91e50039f2072344..da23f52c56d8a1f838d736f9b9318391d7a311b2 100644 (file)
@@ -188,7 +188,7 @@ inline const FontCascade& InlineTextBox::lineFont() const
     return combinedText() ? combinedText()->textCombineFont() : lineStyle().fontCascade();
 }
 
-// FIXME: Share more code with paintSelection().
+// FIXME: Share more code with paintTextSubrangeBackground().
 LayoutRect InlineTextBox::localSelectionRect(unsigned startPos, unsigned endPos) const
 {
     unsigned sPos = clampedOffset(startPos);
@@ -204,11 +204,11 @@ LayoutRect InlineTextBox::localSelectionRect(unsigned startPos, unsigned endPos)
     TextRun textRun = createTextRun(text);
 
     LayoutRect selectionRect = LayoutRect(LayoutPoint(logicalLeft(), selectionTop), LayoutSize(m_logicalWidth, selectionHeight));
-    // Avoid computing the font width when the entire line box is selected as an optimization.
+    // Avoid measuring the text when the entire line box is selected as an optimization.
     if (sPos || ePos != textRun.length())
         lineFont().adjustSelectionRectForText(textRun, selectionRect, sPos, ePos);
     // FIXME: The computation of the snapped selection rect differs from the computation of this rect
-    // in paintSelection(). See <https://bugs.webkit.org/show_bug.cgi?id=138913>.
+    // in paintTextSubrangeBackground(). See <https://bugs.webkit.org/show_bug.cgi?id=138913>.
     IntRect snappedSelectionRect = enclosingIntRect(selectionRect);
     LayoutUnit logicalWidth = snappedSelectionRect.width();
     if (snappedSelectionRect.x() > logicalRight())
@@ -392,6 +392,41 @@ bool InlineTextBox::emphasisMarkExistsAndIsAbove(const RenderStyle& style, bool&
     return !rubyText || !rubyText->hasLines();
 }
 
+struct InlineTextBox::MarkerSubrangeStyle {
+    bool operator==(const MarkerSubrangeStyle& other) const
+    {
+        return backgroundColor == other.backgroundColor && textStyles == other.textStyles
+            && textDecorationStyles == other.textDecorationStyles && textShadow == other.textShadow
+            && alpha == other.alpha;
+    }
+    bool operator!=(const MarkerSubrangeStyle& other) const { return !(*this == other); }
+
+    Color backgroundColor;
+    TextPaintStyle textStyles;
+    TextDecorationPainter::Styles textDecorationStyles;
+    const ShadowData* textShadow;
+    float alpha;
+};
+
+struct InlineTextBox::StyledMarkerSubrange : MarkerSubrange {
+    StyledMarkerSubrange(const MarkerSubrange& marker)
+        : MarkerSubrange { marker }
+    {
+    }
+
+    MarkerSubrangeStyle style;
+};
+
+static MarkerSubrange createMarkerSubrangeFromSelectionInBox(const InlineTextBox& box)
+{
+    unsigned selectionStart;
+    unsigned selectionEnd;
+    std::tie(selectionStart, selectionEnd) = box.selectionStartEnd();
+    if (selectionStart < selectionEnd)
+        return { selectionStart, selectionEnd, MarkerSubrange::Selection };
+    return { };
+}
+
 void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/)
 {
     if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer().style().visibility() != VISIBLE
@@ -458,29 +493,24 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset,
     bool containsComposition = renderer().textNode() && renderer().frame().editor().compositionNode() == renderer().textNode();
     bool useCustomUnderlines = containsComposition && renderer().frame().editor().compositionUsesCustomUnderlines();
 
-    // Determine the text colors and selection colors.
-    TextPaintStyle textPaintStyle = computeTextPaintStyle(renderer().frame(), lineStyle, paintInfo);
+    MarkerSubrangeStyle unmarkedStyle = computeStyleForUnmarkedMarkerSubrange(paintInfo);
 
-    bool paintSelectedTextOnly = false;
-    bool paintSelectedTextSeparately = false;
-    bool paintNonSelectedTextOnly = false;
-    const ShadowData* selectionShadow = nullptr;
-    
-    // Text with custom underlines does not have selection background painted, so selection paint style is not appropriate for it.
-    TextPaintStyle selectionPaintStyle = haveSelection && !useCustomUnderlines ? computeTextSelectionPaintStyle(textPaintStyle, renderer(), lineStyle, paintInfo, paintSelectedTextOnly, paintSelectedTextSeparately, paintNonSelectedTextOnly, selectionShadow) : textPaintStyle;
-
-    // Set our font.
-    const FontCascade& font = lineFont();
     // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
     // and composition underlines.
     if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
         if (containsComposition && !useCustomUnderlines)
             paintCompositionBackground(context, boxOrigin);
 
-        paintDocumentMarkers(context, boxOrigin, true);
-
-        if (haveSelection && !useCustomUnderlines)
-            paintSelection(context, boxOrigin, selectionPaintStyle.fillColor);
+        Vector<MarkerSubrange> subranges = collectSubrangesForDocumentMarkers(TextPaintPhase::Background);
+#if ENABLE(TEXT_SELECTION)
+        if (haveSelection && !useCustomUnderlines && !context.paintingDisabled()) {
+            auto selectionSubrange = createMarkerSubrangeFromSelectionInBox(*this);
+            if (!selectionSubrange.isEmpty())
+                subranges.append(WTFMove(selectionSubrange));
+        }
+#endif
+        auto styledSubranges = subdivideAndResolveStyle(subranges, unmarkedStyle, paintInfo);
+        paintMarkerSubranges(context, TextPaintPhase::Background, boxRect, styledSubranges);
     }
 
     // FIXME: Right now, InlineTextBoxes never call addRelevantUnpaintedObject() even though they might
@@ -491,81 +521,60 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset,
         renderer().page().addRelevantRepaintedObject(&renderer(), IntRect(boxOrigin.x(), boxOrigin.y(), logicalWidth(), logicalHeight()));
 
     // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
-    auto text = this->text();
-    TextRun textRun = createTextRun(text);
-    unsigned length = textRun.length();
-    if (m_truncation != cNoTruncation)
-        length = m_truncation;
-
-    unsigned selectionStart = 0;
-    unsigned selectionEnd = 0;
-    if (haveSelection && (paintSelectedTextOnly || paintSelectedTextSeparately))
-        std::tie(selectionStart, selectionEnd) = selectionStartEnd();
-
-    float emphasisMarkOffset = 0;
-    bool emphasisMarkAbove;
-    bool hasTextEmphasis = emphasisMarkExistsAndIsAbove(lineStyle, emphasisMarkAbove);
-    const AtomicString& emphasisMark = hasTextEmphasis ? lineStyle.textEmphasisMarkString() : nullAtom();
-    if (!emphasisMark.isEmpty())
-        emphasisMarkOffset = emphasisMarkAbove ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
-
-    const ShadowData* textShadow = (paintInfo.forceTextColor()) ? nullptr : lineStyle.textShadow();
-
-    FloatPoint textOrigin(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());
-    if (combinedText) {
-        if (auto newOrigin = combinedText->computeTextOrigin(boxRect))
-            textOrigin = newOrigin.value();
+    bool shouldPaintSelectionForeground = haveSelection && !useCustomUnderlines;
+    Vector<MarkerSubrange> subranges;
+    if (paintInfo.phase != PaintPhaseSelection) {
+        // The subranges for the gaps between document markers and selection are implicitly created by subdividing the entire line.
+        subranges.append({ clampedOffset(m_start), clampedOffset(end() + 1), MarkerSubrange::Unmarked });
+        if (!isPrinting) {
+            subranges.appendVector(collectSubrangesForDocumentMarkers(TextPaintPhase::Foreground));
+
+            bool shouldPaintDraggedContent = !(paintInfo.paintBehavior & PaintBehaviorExcludeSelection);
+            if (shouldPaintDraggedContent) {
+                auto subrangesForDraggedContent = collectSubrangesForDraggedContent();
+                if (!subrangesForDraggedContent.isEmpty()) {
+                    shouldPaintSelectionForeground = false;
+                    subranges.appendVector(subrangesForDraggedContent);
+                }
+            }
+        }
+    }
+    // The selection subrange acts as a placeholder when computing the subranges for the gaps...
+    if (shouldPaintSelectionForeground) {
+        ASSERT(!isPrinting);
+        auto selectionSubrange = createMarkerSubrangeFromSelectionInBox(*this);
+        if (!selectionSubrange.isEmpty())
+            subranges.append(WTFMove(selectionSubrange));
     }
 
-    if (isHorizontal())
-        textOrigin.setY(roundToDevicePixel(LayoutUnit(textOrigin.y()), renderer().document().deviceScaleFactor()));
-    else
-        textOrigin.setX(roundToDevicePixel(LayoutUnit(textOrigin.x()), renderer().document().deviceScaleFactor()));
+    auto styledSubranges = subdivideAndResolveStyle(subranges, unmarkedStyle, paintInfo);
 
-    TextPainter textPainter(context);
-    textPainter.setFont(font);
-    textPainter.setStyle(textPaintStyle);
-    textPainter.setSelectionStyle(selectionPaintStyle);
-    textPainter.setIsHorizontal(isHorizontal());
-    textPainter.setShadow(textShadow);
-    textPainter.setSelectionShadow(selectionShadow);
-    textPainter.setEmphasisMark(emphasisMark, emphasisMarkOffset, combinedText);
+    // ... now remove the selection subrange if we are excluding selection.
+    if (!isPrinting && paintInfo.paintBehavior & PaintBehaviorExcludeSelection)
+        styledSubranges.removeAllMatching([] (const StyledMarkerSubrange& subrange) { return subrange.type == MarkerSubrange::Selection; });
 
-    auto draggedContentRanges = renderer().draggedContentRangesBetweenOffsets(m_start, m_start + m_len);
-    if (!draggedContentRanges.isEmpty() && !paintSelectedTextOnly && !paintNonSelectedTextOnly) {
-        // FIXME: Painting with text effects ranges currently only works if we're not also painting the selection.
-        // In the future, we may want to support this capability, but in the meantime, this isn't required by anything.
-        unsigned currentEnd = 0;
-        for (size_t index = 0; index < draggedContentRanges.size(); ++index) {
-            unsigned previousEnd = index ? std::min(draggedContentRanges[index - 1].second, length) : 0;
-            unsigned currentStart = draggedContentRanges[index].first - m_start;
-            currentEnd = std::min(draggedContentRanges[index].second - m_start, length);
-
-            if (previousEnd < currentStart)
-                textPainter.paintRange(textRun, boxRect, textOrigin, previousEnd, currentStart);
-
-            if (currentStart < currentEnd) {
-                context.save();
-                context.setAlpha(0.25);
-                textPainter.paintRange(textRun, boxRect, textOrigin, currentStart, currentEnd);
-                context.restore();
-            }
-        }
-        if (currentEnd < length)
-            textPainter.paintRange(textRun, boxRect, textOrigin, currentEnd, length);
-    } else
-        textPainter.paint(textRun, length, boxRect, textOrigin, selectionStart, selectionEnd, paintSelectedTextOnly, paintSelectedTextSeparately, paintNonSelectedTextOnly);
+    paintMarkerSubranges(context, TextPaintPhase::Foreground, boxRect, styledSubranges);
 
     // Paint decorations
     TextDecoration textDecorations = lineStyle.textDecorationsInEffect();
     if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) {
+        auto text = this->text();
+        TextRun textRun = createTextRun(text);
+        unsigned length = textRun.length();
+        if (m_truncation != cNoTruncation)
+            length = m_truncation;
+        unsigned selectionStart = 0;
+        unsigned selectionEnd = 0;
+        if (haveSelection)
+            std::tie(selectionStart, selectionEnd) = selectionStartEnd();
+
         FloatRect textDecorationSelectionClipOutRect;
         if ((paintInfo.paintBehavior & PaintBehaviorExcludeSelection) && selectionStart < selectionEnd && selectionEnd <= length) {
             textDecorationSelectionClipOutRect = logicalOverflowRect();
             textDecorationSelectionClipOutRect.moveBy(localPaintOffset);
             float logicalWidthBeforeRange;
             float logicalWidthAfterRange;
-            float logicalSelectionWidth = font.widthOfTextRange(textRun, selectionStart, selectionEnd, nullptr, &logicalWidthBeforeRange, &logicalWidthAfterRange);
+            float logicalSelectionWidth = lineFont().widthOfTextRange(textRun, selectionStart, selectionEnd, nullptr, &logicalWidthBeforeRange, &logicalWidthAfterRange);
             // FIXME: Do we need to handle vertical bottom to top text?
             if (!isHorizontal()) {
                 textDecorationSelectionClipOutRect.move(0, logicalWidthBeforeRange);
@@ -578,11 +587,12 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset,
                 textDecorationSelectionClipOutRect.setWidth(logicalSelectionWidth);
             }
         }
-        paintDecoration(context, textRun, textOrigin, boxRect, textDecorations, textPaintStyle, textShadow, textDecorationSelectionClipOutRect);
+        paintMarkerSubranges(context, TextPaintPhase::Decoration, boxRect, styledSubranges, textDecorationSelectionClipOutRect);
     }
 
+    // 3. Paint fancy decorations, including composition underlines and platform-specific underlines for spelling errors, grammar errors, et cetera.
     if (paintInfo.phase == PaintPhaseForeground) {
-        paintDocumentMarkers(context, boxOrigin, false);
+        paintPlatformDocumentMarkers(context, boxOrigin);
 
         if (useCustomUnderlines)
             paintCompositionUnderlines(context, boxOrigin);
@@ -626,142 +636,13 @@ std::pair<unsigned, unsigned> InlineTextBox::selectionStartEnd() const
     return { clampedOffset(start), clampedOffset(end) };
 }
 
-void InlineTextBox::paintSelection(GraphicsContext& context, const FloatPoint& boxOrigin, const Color& textColor)
-{
-#if ENABLE(TEXT_SELECTION)
-    if (context.paintingDisabled())
-        return;
-
-    // See if we have a selection to paint at all.
-    unsigned selectionStart;
-    unsigned selectionEnd;
-    std::tie(selectionStart, selectionEnd) = selectionStartEnd();
-    if (selectionStart >= selectionEnd)
-        return;
-
-    Color c = renderer().selectionBackgroundColor();
-    if (!c.isValid() || c.alpha() == 0)
-        return;
-
-    // If the text color ends up being the same as the selection background, invert the selection
-    // background.
-    if (textColor == c)
-        c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
-
-    GraphicsContextStateSaver stateSaver(context);
-    updateGraphicsContext(context, TextPaintStyle(c)); // Don't draw text at all!
-
-    // Note that if the text is truncated, we let the thing being painted in the truncation
-    // draw its own highlight.
-    auto text = this->text();
-    TextRun textRun = createTextRun(text);
-
-    const RootInlineBox& rootBox = root();
-    LayoutUnit selectionBottom = rootBox.selectionBottom();
-    LayoutUnit selectionTop = rootBox.selectionTopAdjustedForPrecedingBlock();
-
-    LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop;
-    LayoutUnit selectionHeight = std::max<LayoutUnit>(0, selectionBottom - selectionTop);
-
-    LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selectionHeight);
-    lineFont().adjustSelectionRectForText(textRun, selectionRect, selectionStart, selectionEnd);
-
-    // FIXME: Support painting combined text.
-    context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), c);
-#else
-    UNUSED_PARAM(context);
-    UNUSED_PARAM(boxOrigin);
-    UNUSED_PARAM(textColor);
-#endif
-}
-
-inline void InlineTextBox::paintTextSubrangeBackground(GraphicsContext& context, const FloatPoint& boxOrigin, const Color& color, unsigned startOffset, unsigned endOffset)
-{
-    startOffset = clampedOffset(startOffset);
-    endOffset = clampedOffset(endOffset);
-    if (startOffset >= endOffset)
-        return;
-
-    GraphicsContextStateSaver stateSaver { context };
-    updateGraphicsContext(context, TextPaintStyle { color }); // Don't draw text at all!
-
-    // Use same y positioning and height as for selection, so that when the selection and this subrange are on
-    // the same word there are no pieces sticking out.
-    LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
-    LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, 0, selectionHeight());
-
-    auto text = this->text();
-    TextRun textRun = createTextRun(text);
-    lineFont().adjustSelectionRectForText(textRun, selectionRect, startOffset, endOffset);
-
-    // FIXME: Support painting combined text.
-    context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), color);
-}
-
-void InlineTextBox::paintCompositionBackground(GraphicsContext& context, const FloatPoint& boxOrigin)
-{
-    paintTextSubrangeBackground(context, boxOrigin, renderer().frame().editor().compositionStart(), renderer().frame().editor().compositionEnd(), Color::compositionFill);
-}
-
-void InlineTextBox::paintTextMatchMarker(GraphicsContext& context, const FloatPoint& boxOrigin, const MarkerSubrange& subrange, bool isActiveMatch)
-{
-    if (!renderer().frame().editor().markedTextMatchesAreHighlighted())
-        return;
-    auto highlightColor = isActiveMatch ? renderer().theme().platformActiveTextSearchHighlightColor() : renderer().theme().platformInactiveTextSearchHighlightColor();
-    paintTextSubrangeBackground(context, boxOrigin, highlightColor, subrange.startOffset, subrange.endOffset);
-}
-
-static inline void mirrorRTLSegment(float logicalWidth, TextDirection direction, float& start, float width)
+void InlineTextBox::paintPlatformDocumentMarkers(GraphicsContext& context, const FloatPoint& boxOrigin)
 {
-    if (direction == LTR)
-        return;
-    start = logicalWidth - width - start;
-}
-
-void InlineTextBox::paintDecoration(GraphicsContext& context, const TextRun& textRun, const FloatPoint& textOrigin,
-    const FloatRect& boxRect, TextDecoration decoration, TextPaintStyle textPaintStyle, const ShadowData* shadow, const FloatRect& clipOutRect)
-{
-    if (m_truncation == cFullTruncation)
-        return;
-
-    updateGraphicsContext(context, textPaintStyle);
-
-    bool isCombinedText = combinedText();
-    if (isCombinedText)
-        context.concatCTM(rotation(boxRect, Clockwise));
-
-    float start = 0;
-    float width = m_logicalWidth;
-    if (m_truncation != cNoTruncation) {
-        width = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
-        mirrorRTLSegment(m_logicalWidth, direction(), start, width);
-    }
-
-    TextDecorationPainter decorationPainter(context, decoration, renderer(), isFirstLine());
-    decorationPainter.setInlineTextBox(this);
-    decorationPainter.setFont(lineFont());
-    decorationPainter.setWidth(width);
-    decorationPainter.setBaseline(lineStyle().fontMetrics().ascent());
-    decorationPainter.setIsHorizontal(isHorizontal());
-    decorationPainter.addTextShadow(shadow);
-
-    FloatPoint localOrigin = boxRect.location();
-    localOrigin.move(start, 0);
-
-    {
-        GraphicsContextStateSaver stateSaver { context, false };
-        if (!clipOutRect.isEmpty()) {
-            stateSaver.save();
-            context.clipOut(clipOutRect);
-        }
-        decorationPainter.paintTextDecoration(textRun, textOrigin, localOrigin);
-    }
-
-    if (isCombinedText)
-        context.concatCTM(rotation(boxRect, Counterclockwise));
+    for (auto& subrange : subdivide(collectSubrangesForDocumentMarkers(TextPaintPhase::Foreground), OverlapStrategy::Frontmost))
+        paintPlatformDocumentMarker(context, boxOrigin, subrange);
 }
 
-void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const FloatPoint& boxOrigin, const MarkerSubrange& subrange)
+void InlineTextBox::paintPlatformDocumentMarker(GraphicsContext& context, const FloatPoint& boxOrigin, const MarkerSubrange& subrange)
 {
     // Never print spelling/grammar markers (5327887)
     if (renderer().document().printing())
@@ -773,19 +654,8 @@ void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const FloatPoi
     float start = 0; // start of line to draw, relative to tx
     float width = m_logicalWidth; // how much line to draw
 
-    // Determine whether we need to measure text
-    bool markerSpansWholeBox = true;
-    if (m_start <= subrange.startOffset)
-        markerSpansWholeBox = false;
-    if ((end() + 1) != subrange.endOffset) // End points at the last char, not past it
-        markerSpansWholeBox = false;
-    if (m_truncation != cNoTruncation)
-        markerSpansWholeBox = false;
-
-    if (!markerSpansWholeBox) {
-        unsigned startPosition = clampedOffset(subrange.startOffset);
-        unsigned endPosition = clampedOffset(subrange.endOffset);
-
+    // Avoid measuring the text when the entire line box is selected as an optimization.
+    if (subrange.startOffset || subrange.endOffset != clampedOffset(end() + 1)) {
         // Calculate start & width
         int deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
         int selHeight = selectionHeight();
@@ -794,12 +664,12 @@ void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const FloatPoi
         TextRun run = createTextRun(text);
 
         LayoutRect selectionRect = LayoutRect(startPoint, FloatSize(0, selHeight));
-        lineFont().adjustSelectionRectForText(run, selectionRect, startPosition, endPosition);
+        lineFont().adjustSelectionRectForText(run, selectionRect, subrange.startOffset, subrange.endOffset);
         IntRect markerRect = enclosingIntRect(selectionRect);
         start = markerRect.x() - startPoint.x();
         width = markerRect.width();
     }
-    
+
     auto lineStyleForSubrangeType = [] (MarkerSubrange::Type type) {
         switch (type) {
         case MarkerSubrange::SpellingError:
@@ -838,14 +708,100 @@ void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const FloatPoi
         // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
         underlineOffset = baseline + 2;
     }
-    // FIXME: Support painting combined text.
     context.drawLineForDocumentMarker(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, lineStyleForSubrangeType(subrange.type));
 }
 
-void InlineTextBox::paintDocumentMarkers(GraphicsContext& context, const FloatPoint& boxOrigin, bool background)
+auto InlineTextBox::computeStyleForUnmarkedMarkerSubrange(const PaintInfo& paintInfo) const -> MarkerSubrangeStyle
 {
+    auto& lineStyle = this->lineStyle();
+
+    MarkerSubrangeStyle style;
+    style.textDecorationStyles = TextDecorationPainter::stylesForRenderer(renderer(), lineStyle.textDecorationsInEffect(), isFirstLine());
+    style.textStyles = computeTextPaintStyle(renderer().frame(), lineStyle, paintInfo);
+    style.textShadow = paintInfo.forceTextColor() ? nullptr : lineStyle.textShadow();
+    style.alpha = 1;
+    return style;
+}
+
+auto InlineTextBox::resolveStyleForSubrange(const MarkerSubrange& subrange, const MarkerSubrangeStyle& baseStyle, const PaintInfo& paintInfo) -> StyledMarkerSubrange
+{
+    MarkerSubrangeStyle style = baseStyle;
+    switch (subrange.type) {
+    case MarkerSubrange::Correction:
+    case MarkerSubrange::DictationAlternatives:
+#if PLATFORM(IOS)
+    // FIXME: See <rdar://problem/8933352>. Also, remove the PLATFORM(IOS)-guard.
+    case MarkerSubrange::DictationPhraseWithAlternatives:
+#endif
+    case MarkerSubrange::GrammarError:
+    case MarkerSubrange::SpellingError:
+    case MarkerSubrange::Unmarked:
+        break;
+    case MarkerSubrange::DraggedContent:
+        style.alpha = 0.25;
+        break;
+    case MarkerSubrange::Selection: {
+        const ShadowData* selectionShadow = nullptr;
+        style.textStyles = computeTextSelectionPaintStyle(style.textStyles, renderer(), lineStyle(), paintInfo, selectionShadow);
+        style.textShadow = selectionShadow;
+
+        Color selectionBackgroundColor = renderer().selectionBackgroundColor();
+        style.backgroundColor = selectionBackgroundColor;
+        if (selectionBackgroundColor.isValid() && selectionBackgroundColor.alpha() && style.textStyles.fillColor == selectionBackgroundColor)
+            style.backgroundColor = { 0xff - selectionBackgroundColor.red(), 0xff - selectionBackgroundColor.green(), 0xff - selectionBackgroundColor.blue() };
+        break;
+    }
+    case MarkerSubrange::TextMatch:
+        style.backgroundColor = subrange.marker->isActiveMatch() ? renderer().theme().platformActiveTextSearchHighlightColor() : renderer().theme().platformInactiveTextSearchHighlightColor();
+        break;
+    }
+    StyledMarkerSubrange styledSubrange = subrange;
+    styledSubrange.style = WTFMove(style);
+    return styledSubrange;
+}
+
+auto InlineTextBox::subdivideAndResolveStyle(const Vector<MarkerSubrange>& subrangesToSubdivide, const MarkerSubrangeStyle& baseStyle, const PaintInfo& paintInfo) -> Vector<StyledMarkerSubrange>
+{
+    if (subrangesToSubdivide.isEmpty())
+        return { };
+
+    auto areAdjacentSubrangesWithSameStyle = [] (const StyledMarkerSubrange& a, const StyledMarkerSubrange& b) {
+        return a.endOffset == b.startOffset && a.style == b.style;
+    };
+
+    auto subranges = subdivide(subrangesToSubdivide, OverlapStrategy::FrontmostWithLongestEffectiveRange);
+
+    // Coallesce styles of adjacent subranges to minimize the number of drawing commands.
+    Vector<StyledMarkerSubrange> styledSubranges;
+    styledSubranges.reserveInitialCapacity(subranges.size());
+    styledSubranges.uncheckedAppend(resolveStyleForSubrange(subranges[0], baseStyle, paintInfo));
+    for (auto it = subranges.begin() + 1, end = subranges.end(); it != end; ++it) {
+        StyledMarkerSubrange& previousStyledSubrange = styledSubranges.last();
+        auto currentStyledSubrange = resolveStyleForSubrange(*it, baseStyle, paintInfo);
+        if (areAdjacentSubrangesWithSameStyle(previousStyledSubrange, currentStyledSubrange)) {
+            previousStyledSubrange.endOffset = currentStyledSubrange.endOffset;
+            continue;
+        }
+        styledSubranges.uncheckedAppend(currentStyledSubrange);
+    }
+    return styledSubranges;
+}
+
+Vector<MarkerSubrange> InlineTextBox::collectSubrangesForDraggedContent()
+{
+    using DraggendContentRange = std::pair<unsigned, unsigned>;
+    auto draggedContentRanges = renderer().draggedContentRangesBetweenOffsets(m_start, m_start + m_len);
+    Vector<MarkerSubrange> result = draggedContentRanges.map([this] (const DraggendContentRange& range) -> MarkerSubrange {
+        return { clampedOffset(range.first), clampedOffset(range.second), MarkerSubrange::DraggedContent };
+    });
+    return result;
+}
+
+Vector<MarkerSubrange> InlineTextBox::collectSubrangesForDocumentMarkers(TextPaintPhase phase)
+{
+    ASSERT(phase == TextPaintPhase::Background || phase == TextPaintPhase::Foreground);
     if (!renderer().textNode())
-        return;
+        return { };
 
     Vector<RenderedDocumentMarker*> markers = renderer().document().markers().markersFor(renderer().textNode());
 
@@ -876,72 +832,221 @@ void InlineTextBox::paintDocumentMarkers(GraphicsContext& context, const FloatPo
     // Give any document markers that touch this run a chance to draw before the text has been drawn.
     // Note end() points at the last char, not one past it like endOffset and ranges do.
     for (auto* marker : markers) {
-        // Paint either the background markers or the foreground markers, but not both
+        // Collect either the background markers or the foreground markers, but not both
         switch (marker->type()) {
-            case DocumentMarker::Grammar:
-            case DocumentMarker::Spelling:
-            case DocumentMarker::CorrectionIndicator:
-            case DocumentMarker::Replacement:
-            case DocumentMarker::DictationAlternatives:
+        case DocumentMarker::Grammar:
+        case DocumentMarker::Spelling:
+        case DocumentMarker::CorrectionIndicator:
+        case DocumentMarker::Replacement:
+        case DocumentMarker::DictationAlternatives:
 #if PLATFORM(IOS)
-            // FIXME: Remove the PLATFORM(IOS)-guard.
-            case DocumentMarker::DictationPhraseWithAlternatives:
+        // FIXME: Remove the PLATFORM(IOS)-guard.
+        case DocumentMarker::DictationPhraseWithAlternatives:
 #endif
-                if (background)
-                    continue;
-                break;
-            case DocumentMarker::TextMatch:
+            if (phase == TextPaintPhase::Background)
+                continue;
+            break;
+        case DocumentMarker::TextMatch:
+            if (!renderer().frame().editor().markedTextMatchesAreHighlighted())
+                continue;
+            FALLTHROUGH;
 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
-            case DocumentMarker::TelephoneNumber:
+        case DocumentMarker::TelephoneNumber:
 #endif
-                if (!background)
-                    continue;
-                break;
-            default:
+            if (phase == TextPaintPhase::Foreground)
                 continue;
+            break;
+        default:
+            continue;
         }
 
-        if (marker->endOffset() <= start())
-            // marker is completely before this run.  This might be a marker that sits before the
+        if (marker->endOffset() <= start()) {
+            // Marker is completely before this run. This might be a marker that sits before the
             // first run we draw, or markers that were within runs we skipped due to truncation.
             continue;
-        
-        if (marker->startOffset() > end())
-            // marker is completely after this run, bail.  A later run will paint it.
+        }
+
+        if (marker->startOffset() > end()) {
+            // Marker is completely after this run, bail. A later run will paint it.
             break;
-        
-        // marker intersects this run.  Paint it.
+        }
+
+        // Marker intersects this run. Collect it.
         switch (marker->type()) {
-            case DocumentMarker::Spelling:
-            case DocumentMarker::CorrectionIndicator:
-            case DocumentMarker::DictationAlternatives:
-            case DocumentMarker::Grammar:
+        case DocumentMarker::Spelling:
+        case DocumentMarker::CorrectionIndicator:
+        case DocumentMarker::DictationAlternatives:
+        case DocumentMarker::Grammar:
 #if PLATFORM(IOS)
-            // FIXME: See <rdar://problem/8933352>. Also, remove the PLATFORM(IOS)-guard.
-            case DocumentMarker::DictationPhraseWithAlternatives:
+        // FIXME: See <rdar://problem/8933352>. Also, remove the PLATFORM(IOS)-guard.
+        case DocumentMarker::DictationPhraseWithAlternatives:
 #endif
-            case DocumentMarker::TextMatch:
-                subranges.uncheckedAppend({ marker->startOffset(), marker->endOffset(), markerTypeForSubrangeType(marker->type()), marker });
-                break;
-            case DocumentMarker::Replacement:
-                break;
+        case DocumentMarker::TextMatch:
+            subranges.uncheckedAppend({ clampedOffset(marker->startOffset()), clampedOffset(marker->endOffset()), markerTypeForSubrangeType(marker->type()), marker });
+            break;
+        case DocumentMarker::Replacement:
+            break;
 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
-            case DocumentMarker::TelephoneNumber:
-                break;
+        case DocumentMarker::TelephoneNumber:
+            break;
 #endif
-            default:
-                ASSERT_NOT_REACHED();
+        default:
+            ASSERT_NOT_REACHED();
         }
     }
+    return subranges;
+}
 
-    for (auto& subrange : subdivide(subranges, OverlapStrategy::Frontmost)) {
-        if (subrange.type == MarkerSubrange::TextMatch)
-            paintTextMatchMarker(context, boxOrigin, subrange, subrange.marker->isActiveMatch());
-        else
-            paintDocumentMarker(context, boxOrigin, subrange);
+FloatPoint InlineTextBox::textOriginFromBoxRect(const FloatRect& boxRect) const
+{
+    FloatPoint textOrigin { boxRect.x(), boxRect.y() + lineFont().fontMetrics().ascent() };
+    if (auto* combinedText = this->combinedText()) {
+        if (auto newOrigin = combinedText->computeTextOrigin(boxRect))
+            textOrigin = newOrigin.value();
+    }
+    if (isHorizontal())
+        textOrigin.setY(roundToDevicePixel(LayoutUnit { textOrigin.y() }, renderer().document().deviceScaleFactor()));
+    else
+        textOrigin.setX(roundToDevicePixel(LayoutUnit { textOrigin.x() }, renderer().document().deviceScaleFactor()));
+    return textOrigin;
+}
+
+void InlineTextBox::paintMarkerSubranges(GraphicsContext& context, TextPaintPhase phase, const FloatRect& boxRect, const Vector<StyledMarkerSubrange>& subranges, const FloatRect& decorationClipOutRect)
+{
+    switch (phase) {
+    case TextPaintPhase::Background:
+        for (auto& subrange : subranges)
+            paintTextSubrangeBackground(context, boxRect.location(), subrange.style.backgroundColor, subrange.startOffset, subrange.endOffset);
+        return;
+    case TextPaintPhase::Foreground:
+        for (auto& subrange : subranges)
+            paintTextSubrangeForeground(context, boxRect, subrange);
+        return;
+    case TextPaintPhase::Decoration:
+        for (auto& subrange : subranges)
+            paintTextSubrangeDecoration(context, boxRect, decorationClipOutRect, subrange);
+        return;
     }
 }
 
+void InlineTextBox::paintTextSubrangeBackground(GraphicsContext& context, const FloatPoint& boxOrigin, const Color& color, unsigned clampedStartOffset, unsigned clampedEndOffset)
+{
+    if (clampedStartOffset >= clampedEndOffset)
+        return;
+
+    GraphicsContextStateSaver stateSaver { context };
+    updateGraphicsContext(context, TextPaintStyle { color }); // Don't draw text at all!
+
+    // Note that if the text is truncated, we let the thing being painted in the truncation
+    // draw its own highlight.
+    auto text = this->text();
+    TextRun textRun = createTextRun(text);
+
+    const RootInlineBox& rootBox = root();
+    LayoutUnit selectionBottom = rootBox.selectionBottom();
+    LayoutUnit selectionTop = rootBox.selectionTopAdjustedForPrecedingBlock();
+
+    // Use same y positioning and height as for selection, so that when the selection and this subrange are on
+    // the same word there are no pieces sticking out.
+    LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop;
+    LayoutUnit selectionHeight = std::max<LayoutUnit>(0, selectionBottom - selectionTop);
+
+    LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selectionHeight);
+    lineFont().adjustSelectionRectForText(textRun, selectionRect, clampedStartOffset, clampedEndOffset);
+
+    // FIXME: Support painting combined text. See <https://bugs.webkit.org/show_bug.cgi?id=180993>.
+    context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), color);
+}
+
+void InlineTextBox::paintTextSubrangeForeground(GraphicsContext& context, const FloatRect& boxRect, const StyledMarkerSubrange& subrange)
+{
+    if (subrange.startOffset >= subrange.endOffset)
+        return;
+
+    const FontCascade& font = lineFont();
+    const RenderStyle& lineStyle = this->lineStyle();
+
+    float emphasisMarkOffset = 0;
+    bool emphasisMarkAbove;
+    bool hasTextEmphasis = emphasisMarkExistsAndIsAbove(lineStyle, emphasisMarkAbove);
+    const AtomicString& emphasisMark = hasTextEmphasis ? lineStyle.textEmphasisMarkString() : nullAtom();
+    if (!emphasisMark.isEmpty())
+        emphasisMarkOffset = emphasisMarkAbove ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
+
+    TextPainter textPainter { context };
+    textPainter.setFont(font);
+    textPainter.setStyle(subrange.style.textStyles);
+    textPainter.setIsHorizontal(isHorizontal());
+    textPainter.setShadow(subrange.style.textShadow);
+    textPainter.setEmphasisMark(emphasisMark, emphasisMarkOffset, combinedText());
+
+    GraphicsContextStateSaver stateSaver { context, false };
+    if (subrange.type == MarkerSubrange::DraggedContent) {
+        stateSaver.save();
+        context.setAlpha(subrange.style.alpha);
+    }
+    // TextPainter wants the box rectangle and text origin of the entire line box.
+    auto text = this->text();
+    textPainter.paintRange(createTextRun(text), boxRect, textOriginFromBoxRect(boxRect), subrange.startOffset, subrange.endOffset);
+}
+
+void InlineTextBox::paintTextSubrangeDecoration(GraphicsContext& context, const FloatRect& boxRect, const FloatRect& clipOutRect, const StyledMarkerSubrange& subrange)
+{
+    if (m_truncation == cFullTruncation)
+        return;
+
+    updateGraphicsContext(context, subrange.style.textStyles);
+
+    bool isCombinedText = combinedText();
+    if (isCombinedText)
+        context.concatCTM(rotation(boxRect, Clockwise));
+
+    // 1. Compute text selection
+    unsigned startOffset = subrange.startOffset;
+    unsigned endOffset = subrange.endOffset;
+    if (startOffset >= endOffset)
+        return;
+
+    // Note that if the text is truncated, we let the thing being painted in the truncation
+    // draw its own decoration.
+    auto text = this->text();
+    TextRun textRun = createTextRun(text);
+
+    // Avoid measuring the text when the entire line box is selected as an optimization.
+    FloatRect snappedSelectionRect = boxRect;
+    if (startOffset || endOffset != textRun.length()) {
+        LayoutRect selectionRect = { boxRect.x(), boxRect.y(), boxRect.width(), boxRect.height() };
+        lineFont().adjustSelectionRectForText(textRun, selectionRect, startOffset, endOffset);
+        snappedSelectionRect = snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr());
+    }
+
+    // 2. Paint
+    TextDecorationPainter decorationPainter { context, static_cast<unsigned>(lineStyle().textDecorationsInEffect()), renderer(), isFirstLine(), subrange.style.textDecorationStyles };
+    decorationPainter.setInlineTextBox(this);
+    decorationPainter.setFont(lineFont());
+    decorationPainter.setWidth(snappedSelectionRect.width());
+    decorationPainter.setBaseline(lineStyle().fontMetrics().ascent());
+    decorationPainter.setIsHorizontal(isHorizontal());
+    decorationPainter.addTextShadow(subrange.style.textShadow);
+
+    {
+        GraphicsContextStateSaver stateSaver { context, false };
+        if (!clipOutRect.isEmpty()) {
+            stateSaver.save();
+            context.clipOut(clipOutRect);
+        }
+        decorationPainter.paintTextDecoration(textRun.subRun(startOffset, endOffset - startOffset), textOriginFromBoxRect(snappedSelectionRect), snappedSelectionRect.location());
+    }
+
+    if (isCombinedText)
+        context.concatCTM(rotation(boxRect, Counterclockwise));
+}
+
+void InlineTextBox::paintCompositionBackground(GraphicsContext& context, const FloatPoint& boxOrigin)
+{
+    paintTextSubrangeBackground(context, boxOrigin, clampedOffset(renderer().frame().editor().compositionStart()), clampedOffset(renderer().frame().editor().compositionEnd()), Color::compositionFill);
+}
+
 void InlineTextBox::paintCompositionUnderlines(GraphicsContext& context, const FloatPoint& boxOrigin) const
 {
     if (m_truncation == cFullTruncation)
@@ -966,6 +1071,13 @@ void InlineTextBox::paintCompositionUnderlines(GraphicsContext& context, const F
     }
 }
 
+static inline void mirrorRTLSegment(float logicalWidth, TextDirection direction, float& start, float width)
+{
+    if (direction == LTR)
+        return;
+    start = logicalWidth - width - start;
+}
+
 void InlineTextBox::paintCompositionUnderline(GraphicsContext& context, const FloatPoint& boxOrigin, const CompositionUnderline& underline) const
 {
     if (m_truncation == cFullTruncation)
index 8397dd1d0d64bf49d29ba500599e8d72672a8685..79b107854595e1a71e9418c2149fe753be53ab9d 100644 (file)
@@ -151,19 +151,32 @@ public:
     virtual float positionForOffset(unsigned offset) const;
 
 private:
-    void paintDecoration(GraphicsContext&, const TextRun&, const FloatPoint& textOrigin, const FloatRect& boxRect,
-        TextDecoration, TextPaintStyle, const ShadowData*, const FloatRect& clipOutRect);
-    void paintSelection(GraphicsContext&, const FloatPoint& boxOrigin, const Color&);
+    struct MarkerSubrangeStyle;
+    struct StyledMarkerSubrange;
 
-    void paintDocumentMarker(GraphicsContext&, const FloatPoint& boxOrigin, const MarkerSubrange&);
-    void paintDocumentMarkers(GraphicsContext&, const FloatPoint& boxOrigin, bool background);
-    void paintTextMatchMarker(GraphicsContext&, const FloatPoint& boxOrigin, const MarkerSubrange&, bool isActiveMatch);
+    enum class TextPaintPhase { Background, Foreground, Decoration };
+
+    Vector<MarkerSubrange> collectSubrangesForDraggedContent();
+    Vector<MarkerSubrange> collectSubrangesForDocumentMarkers(TextPaintPhase);
+
+    MarkerSubrangeStyle computeStyleForUnmarkedMarkerSubrange(const PaintInfo&) const;
+    StyledMarkerSubrange resolveStyleForSubrange(const MarkerSubrange&, const MarkerSubrangeStyle& baseStyle, const PaintInfo&);
+    Vector<StyledMarkerSubrange> subdivideAndResolveStyle(const Vector<MarkerSubrange>&, const MarkerSubrangeStyle& baseStyle, const PaintInfo&);
+
+    FloatPoint textOriginFromBoxRect(const FloatRect&) const;
+
+    void paintMarkerSubranges(GraphicsContext&, TextPaintPhase, const FloatRect& boxRect, const Vector<StyledMarkerSubrange>&, const FloatRect& decorationClipOutRect = { });
+
+    void paintPlatformDocumentMarker(GraphicsContext&, const FloatPoint& boxOrigin, const MarkerSubrange&);
+    void paintPlatformDocumentMarkers(GraphicsContext&, const FloatPoint& boxOrigin);
 
     void paintCompositionBackground(GraphicsContext&, const FloatPoint& boxOrigin);
     void paintCompositionUnderlines(GraphicsContext&, const FloatPoint& boxOrigin) const;
     void paintCompositionUnderline(GraphicsContext&, const FloatPoint& boxOrigin, const CompositionUnderline&) const;
 
-    void paintTextSubrangeBackground(GraphicsContext&, const FloatPoint& boxOrigin, const Color&, unsigned startOffset, unsigned endOffset);
+    void paintTextSubrangeBackground(GraphicsContext&, const FloatPoint& boxOrigin, const Color&, unsigned clampedStartOffset, unsigned clampedEndOffset);
+    void paintTextSubrangeForeground(GraphicsContext&, const FloatRect& boxRect, const StyledMarkerSubrange&);
+    void paintTextSubrangeDecoration(GraphicsContext&, const FloatRect& boxRect, const FloatRect& clipOutRect, const StyledMarkerSubrange&);
 
     const RenderCombineText* combinedText() const;
     const FontCascade& lineFont() const;
index 56ff7aff4285caed0f6aa574b22fd7cecf691c97..cb4796d8ff9f02e7e3c17653daa6f6216e49c8f2 100644 (file)
@@ -45,6 +45,7 @@ struct MarkerSubrange {
         DictationPhraseWithAlternatives,
 #endif
         Selection,
+        DraggedContent,
     };
 #if !COMPILER_SUPPORTS(NSDMI_FOR_AGGREGATES)
     MarkerSubrange() = default;
@@ -60,6 +61,13 @@ struct MarkerSubrange {
     unsigned endOffset;
     Type type;
     const RenderedDocumentMarker* marker { nullptr };
+
+    bool isEmpty() const { return endOffset <= startOffset; }
+    bool operator!=(const MarkerSubrange& other) const { return !(*this == other); }
+    bool operator==(const MarkerSubrange& other) const
+    {
+        return startOffset == other.startOffset && endOffset == other.endOffset && type == other.type && marker == other.marker;
+    }
 };
 
 enum class OverlapStrategy { None, Frontmost, FrontmostWithLongestEffectiveRange };
index e54352dfb6b90cc294f8a410c8d59ba2c5b5882d..7bed2ac82f60ac551731f6c348351b770bd2a996 100644 (file)
@@ -117,10 +117,10 @@ void paintFlow(const RenderBlockFlow& flow, const Layout& layout, PaintInfo& pai
         if (run.hasHyphen())
             textWithHyphen = run.textWithHyphen();
         // x position indicates the line offset from the rootbox. It's always 0 in case of simple line layout.
-        TextRun textRun(run.hasHyphen() ? textWithHyphen : run.text(), 0, run.expansion(), run.expansionBehavior());
+        TextRun textRun { run.hasHyphen() ? textWithHyphen : run.text(), 0, run.expansion(), run.expansionBehavior() };
         textRun.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
-        FloatPoint textOrigin = FloatPoint(rect.x() + paintOffset.x(), roundToDevicePixel(run.baselinePosition() + paintOffset.y(), deviceScaleFactor));
-        textPainter.paint(textRun, textRun.length(), rect, textOrigin);
+        FloatPoint textOrigin { rect.x() + paintOffset.x(), roundToDevicePixel(run.baselinePosition() + paintOffset.y(), deviceScaleFactor) };
+        textPainter.paint(textRun, rect, textOrigin);
         if (textDecorationPainter) {
             textDecorationPainter->setWidth(rect.width());
             textDecorationPainter->paintTextDecoration(textRun, textOrigin, rect.location() + paintOffset);
index ad1d7ae6aa23167022a104a00bf54362f8807ad5..4159b157b9de5e47553a8d4c0ebd272866895ace 100644 (file)
@@ -241,12 +241,18 @@ static StrokeStyle textDecorationStyleToStrokeStyle(TextDecorationStyle decorati
     return strokeStyle;
 }
 
-TextDecorationPainter::TextDecorationPainter(GraphicsContext& context, unsigned decorations, const RenderText& renderer, bool isFirstLine, PseudoId pseudoId)
+bool TextDecorationPainter::Styles::operator==(const Styles& other) const
+{
+    return underlineColor == other.underlineColor && overlineColor == other.overlineColor && linethroughColor == other.linethroughColor
+        && underlineStyle == other.underlineStyle && overlineStyle == other.overlineStyle && linethroughStyle == other.linethroughStyle;
+}
+
+TextDecorationPainter::TextDecorationPainter(GraphicsContext& context, unsigned decorations, const RenderText& renderer, bool isFirstLine, std::optional<Styles> styles)
     : m_context { context }
     , m_decorations { OptionSet<TextDecoration>::fromRaw(decorations) }
     , m_wavyOffset { wavyOffsetFromDecoration() }
     , m_isPrinting { renderer.document().printing() }
-    , m_styles { stylesForRenderer(renderer, decorations, isFirstLine, pseudoId) }
+    , m_styles { styles ? *WTFMove(styles) : stylesForRenderer(renderer, decorations, isFirstLine, NOPSEUDO) }
     , m_lineStyle { isFirstLine ? renderer.firstLineStyle() : renderer.style() }
 {
 }
index c2b6f6e8f1a01993b4a39e78d759a0478080912b..ec47498214a347d64610edd16477528f754bb92a 100644 (file)
@@ -42,7 +42,8 @@ class TextRun;
 class TextDecorationPainter {
 public:
     // FIXME: Make decorations an OptionSet<TextDecoration>. See <https://bugs.webkit.org/show_bug.cgi?id=176844>.
-    TextDecorationPainter(GraphicsContext&, unsigned decorations, const RenderText&, bool isFirstLine, PseudoId = NOPSEUDO);
+    struct Styles;
+    TextDecorationPainter(GraphicsContext&, unsigned decorations, const RenderText&, bool isFirstLine, std::optional<Styles> = std::nullopt);
     
     void setInlineTextBox(const InlineTextBox* inlineTextBox) { m_inlineTextBox = inlineTextBox; }
     void setFont(const FontCascade& font) { m_font = &font; }
@@ -54,6 +55,9 @@ public:
     void paintTextDecoration(const TextRun&, const FloatPoint& textOrigin, const FloatPoint& boxOrigin);
 
     struct Styles {
+        bool operator==(const Styles&) const;
+        bool operator!=(const Styles& other) const { return !(*this == other); }
+
         Color underlineColor;
         Color overlineColor;
         Color linethroughColor;
@@ -63,7 +67,7 @@ public:
     };
     // FIXME: Make requestedDecorations an OptionSet<TextDecoration>. See <https://bugs.webkit.org/show_bug.cgi?id=176844>.
     static Styles stylesForRenderer(const RenderObject&, unsigned requestedDecorations, bool firstLineStyle = false, PseudoId = NOPSEUDO);
-        
+
 private:
     GraphicsContext& m_context;
     OptionSet<TextDecoration> m_decorations;
index 14a342db004ab861a6396ea22f45922ed9fb8b92..e7e3465820ffa668588d4e3b227aeaf195080235 100644 (file)
@@ -45,6 +45,16 @@ TextPaintStyle::TextPaintStyle(const Color& color)
 {
 }
 
+bool TextPaintStyle::operator==(const TextPaintStyle& other) const
+{
+    return fillColor == other.fillColor && strokeColor == other.strokeColor && emphasisMarkColor == other.emphasisMarkColor
+        && strokeWidth == other.strokeWidth && paintOrder == other.paintOrder && lineJoin == other.lineJoin
+#if ENABLE(LETTERPRESS)
+        && useLetterpressEffect == other.useLetterpressEffect
+#endif
+        && lineCap == other.lineCap && miterLimit == other.miterLimit;
+}
+
 bool textColorIsLegibleAgainstBackgroundColor(const Color& textColor, const Color& backgroundColor)
 {
     // Semi-arbitrarily chose 65025 (255^2) value here after a few tests.
@@ -122,52 +132,34 @@ TextPaintStyle computeTextPaintStyle(const Frame& frame, const RenderStyle& line
     return paintStyle;
 }
 
-TextPaintStyle computeTextSelectionPaintStyle(const TextPaintStyle& textPaintStyle, const RenderText& renderer, const RenderStyle& lineStyle, const PaintInfo& paintInfo, bool& paintSelectedTextOnly, bool& paintSelectedTextSeparately, bool& paintNonSelectedTextOnly, const ShadowData*& selectionShadow)
+TextPaintStyle computeTextSelectionPaintStyle(const TextPaintStyle& textPaintStyle, const RenderText& renderer, const RenderStyle& lineStyle, const PaintInfo& paintInfo, const ShadowData*& selectionShadow)
 {
-    paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection);
-    paintSelectedTextSeparately = paintInfo.paintBehavior & PaintBehaviorExcludeSelection;
-    paintNonSelectedTextOnly = paintInfo.paintBehavior & PaintBehaviorExcludeSelection;
     selectionShadow = (paintInfo.forceTextColor()) ? nullptr : lineStyle.textShadow();
 
     TextPaintStyle selectionPaintStyle = textPaintStyle;
 
 #if ENABLE(TEXT_SELECTION)
     Color foreground = paintInfo.forceTextColor() ? paintInfo.forcedTextColor() : renderer.selectionForegroundColor();
-    if (foreground.isValid() && foreground != selectionPaintStyle.fillColor) {
-        if (!paintSelectedTextOnly)
-            paintSelectedTextSeparately = true;
+    if (foreground.isValid() && foreground != selectionPaintStyle.fillColor)
         selectionPaintStyle.fillColor = foreground;
-    }
 
     Color emphasisMarkForeground = paintInfo.forceTextColor() ? paintInfo.forcedTextColor() : renderer.selectionEmphasisMarkColor();
-    if (emphasisMarkForeground.isValid() && emphasisMarkForeground != selectionPaintStyle.emphasisMarkColor) {
-        if (!paintSelectedTextOnly)
-            paintSelectedTextSeparately = true;
+    if (emphasisMarkForeground.isValid() && emphasisMarkForeground != selectionPaintStyle.emphasisMarkColor)
         selectionPaintStyle.emphasisMarkColor = emphasisMarkForeground;
-    }
 
     if (auto* pseudoStyle = renderer.getCachedPseudoStyle(SELECTION)) {
         const ShadowData* shadow = paintInfo.forceTextColor() ? nullptr : pseudoStyle->textShadow();
-        if (shadow != selectionShadow) {
-            if (!paintSelectedTextOnly)
-                paintSelectedTextSeparately = true;
+        if (shadow != selectionShadow)
             selectionShadow = shadow;
-        }
 
         auto viewportSize = renderer.frame().view() ? renderer.frame().view()->size() : IntSize();
         float strokeWidth = pseudoStyle->computedStrokeWidth(viewportSize);
-        if (strokeWidth != selectionPaintStyle.strokeWidth) {
-            if (!paintSelectedTextOnly)
-                paintSelectedTextSeparately = true;
+        if (strokeWidth != selectionPaintStyle.strokeWidth)
             selectionPaintStyle.strokeWidth = strokeWidth;
-        }
 
         Color stroke = paintInfo.forceTextColor() ? paintInfo.forcedTextColor() : pseudoStyle->computedStrokeColor();
-        if (stroke != selectionPaintStyle.strokeColor) {
-            if (!paintSelectedTextOnly)
-                paintSelectedTextSeparately = true;
+        if (stroke != selectionPaintStyle.strokeColor)
             selectionPaintStyle.strokeColor = stroke;
-        }
     }
 #else
     UNUSED_PARAM(renderer);
index e700e920d301ff6d645c1baeedaac08e44ce9118..0c02844b13f326a207455397da2e0bd684c5918c 100644 (file)
@@ -42,6 +42,9 @@ struct TextPaintStyle {
     TextPaintStyle() { }
     TextPaintStyle(const Color&);
 
+    bool operator==(const TextPaintStyle&) const;
+    bool operator!=(const TextPaintStyle& other) const { return !(*this == other); }
+
     Color fillColor;
     Color strokeColor;
     Color emphasisMarkColor;
@@ -57,7 +60,7 @@ struct TextPaintStyle {
 
 bool textColorIsLegibleAgainstBackgroundColor(const Color& textColor, const Color& backgroundColor);
 TextPaintStyle computeTextPaintStyle(const Frame&, const RenderStyle&, const PaintInfo&);
-TextPaintStyle computeTextSelectionPaintStyle(const TextPaintStyle&, const RenderText&, const RenderStyle&, const PaintInfo&, bool& paintSelectedTextOnly, bool& paintSelectedTextSeparately, bool& paintNonSelectedTextOnly, const ShadowData*& selectionShadow);
+TextPaintStyle computeTextSelectionPaintStyle(const TextPaintStyle&, const RenderText&, const RenderStyle&, const PaintInfo&, const ShadowData*& selectionShadow);
 
 enum FillColorType { UseNormalFillColor, UseEmphasisMarkColor };
 void updateGraphicsContext(GraphicsContext&, const TextPaintStyle&, FillColorType = UseNormalFillColor);
index 50a7b42a7a85a9a662651ce5e07df4cf917875b8..e707f767b974b24a6885a3886ea99f14cb937292 100644 (file)
@@ -180,6 +180,11 @@ void TextPainter::paintTextAndEmphasisMarksIfNeeded(const TextRun& textRun, cons
         m_context.concatCTM(rotation(boxRect, Counterclockwise));
 }
 
+void TextPainter::paint(const TextRun& textRun, const FloatRect& boxRect, const FloatPoint& textOrigin)
+{
+    paintRange(textRun, boxRect, textOrigin, 0, textRun.length());
+}
+
 void TextPainter::paintRange(const TextRun& textRun, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned start, unsigned end)
 {
     ASSERT(m_font);
@@ -189,38 +194,5 @@ void TextPainter::paintRange(const TextRun& textRun, const FloatRect& boxRect, c
     updateGraphicsContext(m_context, m_style);
     paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, start, end, m_style, m_shadow);
 }
-    
-void TextPainter::paint(const TextRun& textRun, unsigned length, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned selectionStart, unsigned selectionEnd,
-    bool paintSelectedTextOnly, bool paintSelectedTextSeparately, bool paintNonSelectedTextOnly)
-{
-    ASSERT(m_font);
-    ASSERT(length <= textRun.length());
-    if (!paintSelectedTextOnly) {
-        // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side
-        // effect, so only when we know we're stroking, do a save/restore.
-        GraphicsContextStateSaver stateSaver(m_context, m_style.strokeWidth > 0);
-        updateGraphicsContext(m_context, m_style);
-        bool fullPaint = !paintSelectedTextSeparately || selectionEnd <= selectionStart;
-        if (fullPaint)
-            paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, 0, length, m_style, m_shadow);
-        else {
-            // Paint the before and after selection parts.
-            if (selectionStart > 0)
-                paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, 0, selectionStart, m_style, m_shadow);
-            if (selectionEnd < length)
-                paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, selectionEnd, length, m_style, m_shadow);
-        }
-    }
-
-    if (paintNonSelectedTextOnly)
-        return;
-
-    // Paint only the text that is selected.
-    if ((paintSelectedTextOnly || paintSelectedTextSeparately) && selectionStart < selectionEnd) {
-        GraphicsContextStateSaver stateSaver(m_context, m_selectionStyle.strokeWidth > 0);
-        updateGraphicsContext(m_context, m_selectionStyle);
-        paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, selectionStart, selectionEnd, m_selectionStyle, m_selectionShadow);
-    }
-}
 
 } // namespace WebCore
index 6dc104bc9d01e54efe4038cdc388a1901bc87f72..afb2697541bcc48722bd7a48b39f56411afc5afa 100644 (file)
@@ -48,21 +48,15 @@ static inline AffineTransform rotation(const FloatRect& boxRect, RotationDirecti
 class TextPainter {
 public:
     TextPainter(GraphicsContext&);
-    
-    void setStyle(const TextPaintStyle& textPaintStyle) { m_style = textPaintStyle; }
-    void setSelectionStyle(const TextPaintStyle& selectionPaintStyle) { m_selectionStyle = selectionPaintStyle; }
 
+    void setStyle(const TextPaintStyle& textPaintStyle) { m_style = textPaintStyle; }
     void setShadow(const ShadowData* shadow) { m_shadow = shadow; }
-    void setSelectionShadow(const ShadowData* selectionShadow) { m_selectionShadow = selectionShadow; }
-
     void setFont(const FontCascade& font) { m_font = &font; }
-
     void setIsHorizontal(bool isHorizontal) { m_textBoxIsHorizontal = isHorizontal; }
-
     void setEmphasisMark(const AtomicString& mark, float offset, const RenderCombineText*);
 
+    void paint(const TextRun&, const FloatRect& boxRect, const FloatPoint& textOrigin);
     void paintRange(const TextRun&, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned start, unsigned end);
-    void paint(const TextRun&, unsigned length, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned selectionStart = 0, unsigned selectionEnd = 0, bool paintSelectedTextOnly = false, bool paintSelectedTextSeparately = false, bool paintNonSelectedTextOnly = false);
 
 private:
     void paintTextOrEmphasisMarks(const FontCascade&, const TextRun&, const AtomicString& emphasisMark, float emphasisMarkOffset,
@@ -75,9 +69,7 @@ private:
     GraphicsContext& m_context;
     const FontCascade* m_font { nullptr };
     TextPaintStyle m_style;
-    TextPaintStyle m_selectionStyle;
     const ShadowData* m_shadow { nullptr };
-    const ShadowData* m_selectionShadow { nullptr };
     AtomicString m_emphasisMark;
     const RenderCombineText* m_combinedText { nullptr };
     float m_emphasisMarkOffset { 0 };
index cb90a13e5971e392121ec361bb50903c6c9434d8..b5367f8a3d1ff402a05a19dbb23fcb157d7df86a 100644 (file)
@@ -1,3 +1,17 @@
+2017-12-19  Daniel Bates  <dabates@apple.com>
+
+        Implement InlineTextBox painting using marker subranges
+        https://bugs.webkit.org/show_bug.cgi?id=180984
+        <rdar://problem/36139364>
+
+        Reviewed by David Hyatt.
+
+        Update unit tests now that we use subranges for the painting of dragged content.
+
+        * TestWebKitAPI/Tests/WebCore/MarkerSubrange.cpp:
+        (WebCore::operator<<):
+        (WebCore::operator==): Deleted; moved to class MarkerSubrange.
+
 2017-12-19  Daniel Bates  <dabates@apple.com>
 
         Add support for computing the frontmost longest effective marker subrange
index eaecd724ed1ff497b809169b3cb15cbe187a7fc2..d3adcfc698fc842c70c2615210ffef50f223542b 100644 (file)
@@ -45,6 +45,8 @@ std::ostream& operator<<(std::ostream& os, MarkerSubrange::Type type)
     case MarkerSubrange::DictationPhraseWithAlternatives:
         return os << "DictationPhraseWithAlternatives";
 #endif
+    case MarkerSubrange::DraggedContent:
+        return os << "DraggedContent";
     case MarkerSubrange::GrammarError:
         return os << "GrammarError";
     case MarkerSubrange::Selection:
@@ -66,11 +68,6 @@ std::ostream& operator<<(std::ostream& os, const MarkerSubrange& subrange)
     return os << ")";
 }
 
-bool operator==(const MarkerSubrange& a, const MarkerSubrange& b)
-{
-    return a.startOffset == b.startOffset && a.endOffset == b.endOffset && a.type == b.type && a.marker == b.marker;
-}
-
 }
 
 namespace TestWebKitAPI {