Share logic in InlineTextBox to compute selection rect
authordbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 19 Oct 2017 18:55:16 +0000 (18:55 +0000)
committerdbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 19 Oct 2017 18:55:16 +0000 (18:55 +0000)
https://bugs.webkit.org/show_bug.cgi?id=178232
<rdar://problem/34963452>

Reviewed by Zalan Bujtas.

Currently each paint routine in InlineTextBox duplicates similar code to compute the selection
rect it will paint. This change consolidates all the duplication into localSelectionRectWithClampedPositions()
and writes all of the paint operations, except for paintCompositionUnderline(), in terms of it.
We will write paintCompositionUnderline() in terms of localSelectionRectWithClampedPositions()
in a subsequent patch.

We also write localSelectionRect() in terms of localSelectionRectWithClampedPositions(). Ideally
we would have one way to compute the selection rect. However, localSelectionRect() and paintDocumentMarker()
currently expect the enclosing integral rectangle of the selection rectangle. The function
paintDocumentMarker() needs the enclosing integral rectangle to avoid truncating the dot pattern
drawn under marked words (e.g. a spelling error) on Cocoa platforms. With regards to localSelectionRect()
we should look to have it return the actual selection rectangle. See <https://bugs.webkit.org/show_bug.cgi?id=138913>
for more details.

* rendering/InlineTextBox.cpp:
(WebCore::InlineTextBox::localSelectionRect const): Move logic in common with paintSelection() into
localSelectionRectWithClampedPositions() and modified code to use it.
(WebCore::InlineTextBox::localSelectionRectWithClampedPositions const): Added.
(WebCore::InlineTextBox::paint): Store the local paint offset as a LayoutPoint as it is the canonical
data type for representing an offset when painting. Pass the local paint offset instead of the analagous boxOrigin value.
(WebCore::InlineTextBox::paintSelection): Write in terms of localSelectionRectWithClampedPositions().
(WebCore::InlineTextBox::paintTextSubrangeBackground): Ditto.
(WebCore::InlineTextBox::paintCompositionBackground): Ditto.
(WebCore::InlineTextBox::paintTextMatchMarker): Ditto.
(WebCore::InlineTextBox::paintDocumentMarker): Ditto.
(WebCore::InlineTextBox::paintDocumentMarkers): Pass paint offset instead of the analogous boxOrigin value.
* rendering/InlineTextBox.h:

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

Source/WebCore/ChangeLog
Source/WebCore/rendering/InlineTextBox.cpp
Source/WebCore/rendering/InlineTextBox.h

index b32e641..11bed0e 100644 (file)
@@ -1,5 +1,41 @@
 2017-10-19  Daniel Bates  <dabates@apple.com>
 
+        Share logic in InlineTextBox to compute selection rect
+        https://bugs.webkit.org/show_bug.cgi?id=178232
+        <rdar://problem/34963452>
+
+        Reviewed by Zalan Bujtas.
+
+        Currently each paint routine in InlineTextBox duplicates similar code to compute the selection
+        rect it will paint. This change consolidates all the duplication into localSelectionRectWithClampedPositions()
+        and writes all of the paint operations, except for paintCompositionUnderline(), in terms of it.
+        We will write paintCompositionUnderline() in terms of localSelectionRectWithClampedPositions()
+        in a subsequent patch.
+
+        We also write localSelectionRect() in terms of localSelectionRectWithClampedPositions(). Ideally
+        we would have one way to compute the selection rect. However, localSelectionRect() and paintDocumentMarker()
+        currently expect the enclosing integral rectangle of the selection rectangle. The function
+        paintDocumentMarker() needs the enclosing integral rectangle to avoid truncating the dot pattern
+        drawn under marked words (e.g. a spelling error) on Cocoa platforms. With regards to localSelectionRect()
+        we should look to have it return the actual selection rectangle. See <https://bugs.webkit.org/show_bug.cgi?id=138913>
+        for more details.
+
+        * rendering/InlineTextBox.cpp:
+        (WebCore::InlineTextBox::localSelectionRect const): Move logic in common with paintSelection() into
+        localSelectionRectWithClampedPositions() and modified code to use it.
+        (WebCore::InlineTextBox::localSelectionRectWithClampedPositions const): Added.
+        (WebCore::InlineTextBox::paint): Store the local paint offset as a LayoutPoint as it is the canonical
+        data type for representing an offset when painting. Pass the local paint offset instead of the analagous boxOrigin value.
+        (WebCore::InlineTextBox::paintSelection): Write in terms of localSelectionRectWithClampedPositions().
+        (WebCore::InlineTextBox::paintTextSubrangeBackground): Ditto.
+        (WebCore::InlineTextBox::paintCompositionBackground): Ditto.
+        (WebCore::InlineTextBox::paintTextMatchMarker): Ditto.
+        (WebCore::InlineTextBox::paintDocumentMarker): Ditto.
+        (WebCore::InlineTextBox::paintDocumentMarkers): Pass paint offset instead of the analogous boxOrigin value.
+        * rendering/InlineTextBox.h:
+
+2017-10-19  Daniel Bates  <dabates@apple.com>
+
         Referrer policy should be inherited from creator
         https://bugs.webkit.org/show_bug.cgi?id=178403
         <rdar://problem/31546136>
index 2a170a9..c5f0be1 100644 (file)
@@ -185,39 +185,63 @@ inline const FontCascade& InlineTextBox::lineFont() const
     return combinedText() ? combinedText()->textCombineFont() : lineStyle().fontCascade();
 }
 
-// FIXME: Share more code with paintSelection().
-LayoutRect InlineTextBox::localSelectionRect(unsigned startPos, unsigned endPos) const
+// FIXME: This function should return the same rectangle as localSelectionRectWithClampedPositions(..., ..., SelectionRectRounding::None),
+// which represents the rectange of the selected text on the page. We need to update all callers to expect this change.
+// See <https://bugs.webkit.org/show_bug.cgi?id=138913> for more details.
+LayoutRect InlineTextBox::localSelectionRect(unsigned startPosition, unsigned endPosition) const
 {
-    unsigned sPos = clampedOffset(startPos);
-    unsigned ePos = clampedOffset(endPos);
+    if (startPosition > endPosition || endPosition < m_start || startPosition > end() + 1)
+        return { };
+    return localSelectionRectWithClampedPositions(clampedOffset(startPosition), clampedOffset(endPosition), SelectionRectRounding::EnclosingIntRect);
+}
 
-    if (sPos >= ePos && !(startPos == endPos && startPos >= start() && startPos <= (start() + len())))
+LayoutRect InlineTextBox::localSelectionRectWithClampedPositions(unsigned clampedStartPosition, unsigned clampedEndPosition, SelectionRectRounding roundingStrategy) const
+{
+    if (clampedStartPosition > clampedEndPosition)
         return { };
 
-    LayoutUnit selectionTop = this->selectionTop();
-    LayoutUnit selectionHeight = this->selectionHeight();
+    auto selectionTop = root().selectionTopAdjustedForPrecedingBlock();
+    auto selectionBottom = this->selectionBottom();
 
     auto text = this->text();
     TextRun textRun = createTextRun(text);
 
-    LayoutRect selectionRect = LayoutRect(LayoutPoint(logicalLeft(), selectionTop), LayoutSize(m_logicalWidth, selectionHeight));
+    // Use same y positioning and height as used for selected text on the page, so that when some or all of this
+    // subrange is selected on the page there are no pieces sticking out.
+    LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop;
+    auto selectionHeight = root().selectionHeightAdjustedForPrecedingBlock();
+    LayoutRect selectionRect { LayoutPoint { logicalLeft(), logicalTop() - deltaY }, LayoutSize { m_logicalWidth, selectionHeight } };
+
     // Avoid computing the font width 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>.
-    IntRect snappedSelectionRect = enclosingIntRect(selectionRect);
-    LayoutUnit logicalWidth = snappedSelectionRect.width();
-    if (snappedSelectionRect.x() > logicalRight())
-        logicalWidth  = 0;
-    else if (snappedSelectionRect.maxX() > logicalRight())
-        logicalWidth = logicalRight() - snappedSelectionRect.x();
-
-    LayoutPoint topPoint = isHorizontal() ? LayoutPoint(snappedSelectionRect.x(), selectionTop) : LayoutPoint(selectionTop, snappedSelectionRect.x());
-    LayoutUnit width = isHorizontal() ? logicalWidth : selectionHeight;
-    LayoutUnit height = isHorizontal() ? selectionHeight : logicalWidth;
-
-    return LayoutRect(topPoint, LayoutSize(width, height));
+    if (clampedStartPosition || clampedEndPosition != textRun.length())
+        lineFont().adjustSelectionRectForText(textRun, selectionRect, clampedStartPosition, clampedEndPosition);
+
+    LayoutUnit selectionLeft;
+    LayoutUnit selectionWidth;
+    if (roundingStrategy == SelectionRectRounding::EnclosingIntRect) {
+        auto snappedSelectionRect = enclosingIntRect(selectionRect);
+        selectionLeft = snappedSelectionRect.x();
+        selectionWidth = snappedSelectionRect.width();
+        if (snappedSelectionRect.x() > logicalRight())
+            selectionWidth = 0;
+        else if (snappedSelectionRect.maxX() > logicalRight())
+            selectionWidth = logicalRight() - snappedSelectionRect.x();
+    } else {
+        selectionLeft = selectionRect.x();
+        selectionWidth = selectionRect.width();
+    }
+
+    LayoutPoint location;
+    LayoutSize size;
+    if (isHorizontal()) {
+        location = { selectionLeft, selectionTop };
+        size = { selectionWidth, selectionHeight };
+    } else {
+        location = { selectionTop, selectionLeft };
+        size = { selectionHeight, selectionWidth };
+    }
+
+    return { location, size };
 }
 
 void InlineTextBox::deleteLine()
@@ -405,7 +429,7 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset,
     LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
     LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
     
-    FloatPoint localPaintOffset(paintOffset);
+    LayoutPoint localPaintOffset { paintOffset };
     
     if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
         return;
@@ -472,12 +496,12 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset,
     // and composition underlines.
     if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
         if (containsComposition && !useCustomUnderlines)
-            paintCompositionBackground(context, boxOrigin);
+            paintCompositionBackground(context, localPaintOffset);
 
-        paintDocumentMarkers(context, boxOrigin, true);
+        paintDocumentMarkers(context, localPaintOffset, true);
 
         if (haveSelection && !useCustomUnderlines)
-            paintSelection(context, boxOrigin, selectionPaintStyle.fillColor);
+            paintSelection(context, localPaintOffset, selectionPaintStyle.fillColor);
     }
 
     // FIXME: Right now, InlineTextBoxes never call addRelevantUnpaintedObject() even though they might
@@ -579,7 +603,7 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset,
     }
 
     if (paintInfo.phase == PaintPhaseForeground) {
-        paintDocumentMarkers(context, boxOrigin, false);
+        paintDocumentMarkers(context, localPaintOffset, false);
 
         if (useCustomUnderlines)
             paintCompositionUnderlines(context, boxOrigin);
@@ -623,7 +647,7 @@ std::pair<unsigned, unsigned> InlineTextBox::selectionStartEnd() const
     return { clampedOffset(start), clampedOffset(end) };
 }
 
-void InlineTextBox::paintSelection(GraphicsContext& context, const FloatPoint& boxOrigin, const Color& textColor)
+void InlineTextBox::paintSelection(GraphicsContext& context, const LayoutPoint& paintOffset, const Color& textColor)
 {
 #if ENABLE(TEXT_SELECTION)
     if (context.paintingDisabled())
@@ -650,29 +674,19 @@ void InlineTextBox::paintSelection(GraphicsContext& context, const FloatPoint& b
 
     // 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);
+    auto selectionRect = localSelectionRectWithClampedPositions(selectionStart, selectionEnd);
+    selectionRect.moveBy(paintOffset);
 
     // FIXME: Support painting combined text.
-    context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), c);
+    context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), isLeftToRightDirection()), c);
 #else
     UNUSED_PARAM(context);
-    UNUSED_PARAM(boxOrigin);
+    UNUSED_PARAM(paintOffset);
     UNUSED_PARAM(textColor);
 #endif
 }
 
-inline void InlineTextBox::paintTextSubrangeBackground(GraphicsContext& context, const FloatPoint& boxOrigin, const Color& color, unsigned startOffset, unsigned endOffset)
+inline void InlineTextBox::paintTextSubrangeBackground(GraphicsContext& context, const LayoutPoint& paintOffset, const Color& color, unsigned startOffset, unsigned endOffset)
 {
     startOffset = clampedOffset(startOffset);
     endOffset = clampedOffset(endOffset);
@@ -682,30 +696,24 @@ inline void InlineTextBox::paintTextSubrangeBackground(GraphicsContext& context,
     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);
+    auto selectionRect = localSelectionRectWithClampedPositions(startOffset, endOffset);
+    selectionRect.moveBy(paintOffset);
 
     // FIXME: Support painting combined text.
-    context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), color);
+    context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), isLeftToRightDirection()), color);
 }
 
-void InlineTextBox::paintCompositionBackground(GraphicsContext& context, const FloatPoint& boxOrigin)
+void InlineTextBox::paintCompositionBackground(GraphicsContext& context, const LayoutPoint& paintOffset)
 {
-    paintTextSubrangeBackground(context, boxOrigin, renderer().frame().editor().compositionStart(), renderer().frame().editor().compositionEnd(), Color::compositionFill);
+    paintTextSubrangeBackground(context, paintOffset, renderer().frame().editor().compositionStart(), renderer().frame().editor().compositionEnd(), Color::compositionFill);
 }
 
-void InlineTextBox::paintTextMatchMarker(GraphicsContext& context, const FloatPoint& boxOrigin, const MarkerSubrange& subrange, bool isActiveMatch)
+void InlineTextBox::paintTextMatchMarker(GraphicsContext& context, const LayoutPoint& paintOffset, 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);
+    paintTextSubrangeBackground(context, paintOffset, highlightColor, subrange.startOffset, subrange.endOffset);
 }
 
 static inline void mirrorRTLSegment(float logicalWidth, TextDirection direction, float& start, float width)
@@ -758,7 +766,7 @@ void InlineTextBox::paintDecoration(GraphicsContext& context, const TextRun& tex
         context.concatCTM(rotation(boxRect, Counterclockwise));
 }
 
-void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const FloatPoint& boxOrigin, const MarkerSubrange& subrange)
+void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const LayoutPoint& paintOffset, const MarkerSubrange& subrange)
 {
     // Never print spelling/grammar markers (5327887)
     if (renderer().document().printing())
@@ -767,9 +775,6 @@ void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const FloatPoi
     if (m_truncation == cFullTruncation)
         return;
 
-    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)
@@ -779,24 +784,23 @@ void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const FloatPoi
     if (m_truncation != cNoTruncation)
         markerSpansWholeBox = false;
 
-    if (!markerSpansWholeBox) {
+    FloatPoint location;
+    float width;
+    if (markerSpansWholeBox) {
+        location = locationIncludingFlipping();
+        width = m_logicalWidth;
+    } else {
         unsigned startPosition = clampedOffset(subrange.startOffset);
         unsigned endPosition = clampedOffset(subrange.endOffset);
 
-        // Calculate start & width
-        int deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
-        int selHeight = selectionHeight();
-        FloatPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY);
-        auto text = this->text();
-        TextRun run = createTextRun(text);
-
-        LayoutRect selectionRect = LayoutRect(startPoint, FloatSize(0, selHeight));
-        lineFont().adjustSelectionRectForText(run, selectionRect, startPosition, endPosition);
-        IntRect markerRect = enclosingIntRect(selectionRect);
-        start = markerRect.x() - startPoint.x();
-        width = markerRect.width();
+        // We round to the nearest enclosing integral rect to avoid truncating the dot decoration on Cocoa platforms.
+        // FIXME: Find a better place to ensure that the Cocoa dots are not truncated.
+        auto selectionRect = localSelectionRectWithClampedPositions(startPosition, endPosition, SelectionRectRounding::EnclosingIntRect);
+        location = selectionRect.location();
+        width = selectionRect.width();
     }
-    
+    location.moveBy(paintOffset);
+
     auto lineStyleForSubrangeType = [] (MarkerSubrange::Type type) {
         switch (type) {
         case MarkerSubrange::SpellingError:
@@ -836,10 +840,11 @@ void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const FloatPoi
         underlineOffset = baseline + 2;
     }
     // FIXME: Support painting combined text.
-    context.drawLineForDocumentMarker(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, lineStyleForSubrangeType(subrange.type));
+    location.move(0, underlineOffset);
+    context.drawLineForDocumentMarker(location, width, lineStyleForSubrangeType(subrange.type));
 }
 
-void InlineTextBox::paintDocumentMarkers(GraphicsContext& context, const FloatPoint& boxOrigin, bool background)
+void InlineTextBox::paintDocumentMarkers(GraphicsContext& context, const LayoutPoint& paintOffset, bool background)
 {
     if (!renderer().textNode())
         return;
@@ -933,9 +938,9 @@ void InlineTextBox::paintDocumentMarkers(GraphicsContext& context, const FloatPo
 
     for (auto& subrange : subdivide(subranges, OverlapStrategy::Frontmost)) {
         if (subrange.type == MarkerSubrange::TextMatch)
-            paintTextMatchMarker(context, boxOrigin, subrange, subrange.marker->isActiveMatch());
+            paintTextMatchMarker(context, paintOffset, subrange, subrange.marker->isActiveMatch());
         else
-            paintDocumentMarker(context, boxOrigin, subrange);
+            paintDocumentMarker(context, paintOffset, subrange);
     }
 }
 
index ba55d3e..4569fea 100644 (file)
@@ -110,7 +110,7 @@ private:
 public:
     FloatRect calculateBoundaries() const override { return FloatRect(x(), y(), width(), height()); }
 
-    virtual LayoutRect localSelectionRect(unsigned startPos, unsigned endPos) const;
+    virtual LayoutRect localSelectionRect(unsigned startPosition, unsigned endPosition) const;
     bool isSelected(unsigned startPosition, unsigned endPosition) const;
     std::pair<unsigned, unsigned> selectionStartEnd() const;
 
@@ -152,17 +152,20 @@ public:
 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&);
+    void paintSelection(GraphicsContext&, const LayoutPoint& paintOffset, const Color&);
 
-    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);
+    void paintDocumentMarker(GraphicsContext&, const LayoutPoint& paintOffset, const MarkerSubrange&);
+    void paintDocumentMarkers(GraphicsContext&, const LayoutPoint& paintOffset, bool background);
+    void paintTextMatchMarker(GraphicsContext&, const LayoutPoint& paintOffset, const MarkerSubrange&, bool isActiveMatch);
 
-    void paintCompositionBackground(GraphicsContext&, const FloatPoint& boxOrigin);
+    void paintCompositionBackground(GraphicsContext&, const LayoutPoint& paintOffset);
     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 LayoutPoint& paintOffset, const Color&, unsigned startOffset, unsigned endOffset);
+
+    enum class SelectionRectRounding { None, EnclosingIntRect };
+    LayoutRect localSelectionRectWithClampedPositions(unsigned clampedStartPosition, unsigned clampedEndPosition, SelectionRectRounding = SelectionRectRounding::None) const;
 
     const RenderCombineText* combinedText() const;
     const FontCascade& lineFont() const;