9e71a71bafa656aae8cd00f0e35d9003471b1f00
[WebKit-https.git] / Source / WebCore / rendering / InlineTextBox.cpp
1 /*
2  * (C) 1999 Lars Knoll (knoll@kde.org)
3  * (C) 2000 Dirk Mueller (mueller@kde.org)
4  * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  */
22
23 #include "config.h"
24 #include "InlineTextBox.h"
25
26 #include "BreakLines.h"
27 #include "DashArray.h"
28 #include "Document.h"
29 #include "DocumentMarkerController.h"
30 #include "Editor.h"
31 #include "EllipsisBox.h"
32 #include "Frame.h"
33 #include "GraphicsContext.h"
34 #include "HitTestResult.h"
35 #include "ImageBuffer.h"
36 #include "InlineTextBoxStyle.h"
37 #include "MarkedText.h"
38 #include "Page.h"
39 #include "PaintInfo.h"
40 #include "RenderBlock.h"
41 #include "RenderCombineText.h"
42 #include "RenderLineBreak.h"
43 #include "RenderRubyRun.h"
44 #include "RenderRubyText.h"
45 #include "RenderTheme.h"
46 #include "RenderView.h"
47 #include "RenderedDocumentMarker.h"
48 #include "Text.h"
49 #include "TextDecorationPainter.h"
50 #include "TextPaintStyle.h"
51 #include "TextPainter.h"
52 #include <stdio.h>
53 #include <wtf/IsoMallocInlines.h>
54 #include <wtf/text/CString.h>
55 #include <wtf/text/TextStream.h>
56
57 namespace WebCore {
58
59 WTF_MAKE_ISO_ALLOCATED_IMPL(InlineTextBox);
60
61 struct SameSizeAsInlineTextBox : public InlineBox {
62     unsigned variables[1];
63     unsigned short variables2[2];
64     void* pointers[2];
65 };
66
67 COMPILE_ASSERT(sizeof(InlineTextBox) == sizeof(SameSizeAsInlineTextBox), InlineTextBox_should_stay_small);
68
69 typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap;
70 static InlineTextBoxOverflowMap* gTextBoxesWithOverflow;
71
72 InlineTextBox::~InlineTextBox()
73 {
74     if (!knownToHaveNoOverflow() && gTextBoxesWithOverflow)
75         gTextBoxesWithOverflow->remove(this);
76     TextPainter::removeGlyphDisplayList(*this);
77 }
78
79 bool InlineTextBox::hasTextContent() const
80 {
81     if (m_len > 1)
82         return true;
83     if (auto* combinedText = this->combinedText()) {
84         ASSERT(m_len == 1);
85         return !combinedText->combinedStringForRendering().isEmpty();
86     }
87     return m_len;
88 }
89
90 void InlineTextBox::markDirty(bool dirty)
91 {
92     if (dirty) {
93         m_len = 0;
94         m_start = 0;
95     }
96     InlineBox::markDirty(dirty);
97 }
98
99 LayoutRect InlineTextBox::logicalOverflowRect() const
100 {
101     if (knownToHaveNoOverflow() || !gTextBoxesWithOverflow)
102         return enclosingIntRect(logicalFrameRect());
103     return gTextBoxesWithOverflow->get(this);
104 }
105
106 void InlineTextBox::setLogicalOverflowRect(const LayoutRect& rect)
107 {
108     ASSERT(!knownToHaveNoOverflow());
109     if (!gTextBoxesWithOverflow)
110         gTextBoxesWithOverflow = new InlineTextBoxOverflowMap;
111     gTextBoxesWithOverflow->add(this, rect);
112 }
113
114 int InlineTextBox::baselinePosition(FontBaseline baselineType) const
115 {
116     if (!parent())
117         return 0;
118     if (&parent()->renderer() == renderer().parent())
119         return parent()->baselinePosition(baselineType);
120     return downcast<RenderBoxModelObject>(*renderer().parent()).baselinePosition(baselineType, isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
121 }
122
123 LayoutUnit InlineTextBox::lineHeight() const
124 {
125     if (!renderer().parent())
126         return 0;
127     if (&parent()->renderer() == renderer().parent())
128         return parent()->lineHeight();
129     return downcast<RenderBoxModelObject>(*renderer().parent()).lineHeight(isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
130 }
131
132 LayoutUnit InlineTextBox::selectionTop() const
133 {
134     return root().selectionTop();
135 }
136
137 LayoutUnit InlineTextBox::selectionBottom() const
138 {
139     return root().selectionBottom();
140 }
141
142 LayoutUnit InlineTextBox::selectionHeight() const
143 {
144     return root().selectionHeight();
145 }
146
147 bool InlineTextBox::isSelected(unsigned startPosition, unsigned endPosition) const
148 {
149     return clampedOffset(startPosition) < clampedOffset(endPosition);
150 }
151
152 RenderObject::SelectionState InlineTextBox::selectionState()
153 {
154     RenderObject::SelectionState state = renderer().selectionState();
155     if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
156         auto& selection = renderer().view().selection();
157         auto startPos = selection.startPosition();
158         auto endPos = selection.endPosition();
159         // The position after a hard line break is considered to be past its end.
160         ASSERT(start() + len() >= (isLineBreak() ? 1 : 0));
161         unsigned lastSelectable = start() + len() - (isLineBreak() ? 1 : 0);
162
163         bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
164         bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
165         if (start && end)
166             state = RenderObject::SelectionBoth;
167         else if (start)
168             state = RenderObject::SelectionStart;
169         else if (end)
170             state = RenderObject::SelectionEnd;
171         else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
172                  (state == RenderObject::SelectionStart || endPos > lastSelectable))
173             state = RenderObject::SelectionInside;
174         else if (state == RenderObject::SelectionBoth)
175             state = RenderObject::SelectionNone;
176     }
177
178     // If there are ellipsis following, make sure their selection is updated.
179     if (m_truncation != cNoTruncation && root().ellipsisBox()) {
180         EllipsisBox* ellipsis = root().ellipsisBox();
181         if (state != RenderObject::SelectionNone) {
182             unsigned selectionStart;
183             unsigned selectionEnd;
184             std::tie(selectionStart, selectionEnd) = selectionStartEnd();
185             // The ellipsis should be considered to be selected if the end of
186             // the selection is past the beginning of the truncation and the
187             // beginning of the selection is before or at the beginning of the
188             // truncation.
189             ellipsis->setSelectionState(selectionEnd >= m_truncation && selectionStart <= m_truncation ?
190                 RenderObject::SelectionInside : RenderObject::SelectionNone);
191         } else
192             ellipsis->setSelectionState(RenderObject::SelectionNone);
193     }
194
195     return state;
196 }
197
198 inline const FontCascade& InlineTextBox::lineFont() const
199 {
200     return combinedText() ? combinedText()->textCombineFont() : lineStyle().fontCascade();
201 }
202
203 // FIXME: Share more code with paintMarkedTextBackground().
204 LayoutRect InlineTextBox::localSelectionRect(unsigned startPos, unsigned endPos) const
205 {
206     unsigned sPos = clampedOffset(startPos);
207     unsigned ePos = clampedOffset(endPos);
208
209     if (sPos >= ePos && !(startPos == endPos && startPos >= start() && startPos <= (start() + len())))
210         return { };
211
212     LayoutUnit selectionTop = this->selectionTop();
213     LayoutUnit selectionHeight = this->selectionHeight();
214
215     TextRun textRun = createTextRun();
216
217     LayoutRect selectionRect = LayoutRect(LayoutPoint(logicalLeft(), selectionTop), LayoutSize(logicalWidth(), selectionHeight));
218     // Avoid measuring the text when the entire line box is selected as an optimization.
219     if (sPos || ePos != textRun.length())
220         lineFont().adjustSelectionRectForText(textRun, selectionRect, sPos, ePos);
221     // FIXME: The computation of the snapped selection rect differs from the computation of this rect
222     // in paintMarkedTextBackground(). See <https://bugs.webkit.org/show_bug.cgi?id=138913>.
223     IntRect snappedSelectionRect = enclosingIntRect(selectionRect);
224     LayoutUnit logicalWidth = snappedSelectionRect.width();
225     if (snappedSelectionRect.x() > logicalRight())
226         logicalWidth  = 0;
227     else if (snappedSelectionRect.maxX() > logicalRight())
228         logicalWidth = logicalRight() - snappedSelectionRect.x();
229
230     LayoutPoint topPoint = isHorizontal() ? LayoutPoint(snappedSelectionRect.x(), selectionTop) : LayoutPoint(selectionTop, snappedSelectionRect.x());
231     LayoutUnit width = isHorizontal() ? logicalWidth : selectionHeight;
232     LayoutUnit height = isHorizontal() ? selectionHeight : logicalWidth;
233
234     return LayoutRect(topPoint, LayoutSize(width, height));
235 }
236
237 void InlineTextBox::deleteLine()
238 {
239     renderer().removeTextBox(*this);
240     delete this;
241 }
242
243 void InlineTextBox::extractLine()
244 {
245     if (extracted())
246         return;
247
248     renderer().extractTextBox(*this);
249 }
250
251 void InlineTextBox::attachLine()
252 {
253     if (!extracted())
254         return;
255     
256     renderer().attachTextBox(*this);
257 }
258
259 float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, float &truncatedWidth, bool& foundBox)
260 {
261     if (foundBox) {
262         m_truncation = cFullTruncation;
263         return -1;
264     }
265
266     // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
267     float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
268     
269     // Criteria for full truncation:
270     // LTR: the left edge of the ellipsis is to the left of our text run.
271     // RTL: the right edge of the ellipsis is to the right of our text run.
272     bool ltrFullTruncation = flowIsLTR && ellipsisX <= left();
273     bool rtlFullTruncation = !flowIsLTR && ellipsisX >= left() + logicalWidth();
274     if (ltrFullTruncation || rtlFullTruncation) {
275         // Too far.  Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
276         m_truncation = cFullTruncation;
277         foundBox = true;
278         return -1;
279     }
280
281     bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < right());
282     bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > left());
283     if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
284         foundBox = true;
285
286         // The inline box may have different directionality than it's parent.  Since truncation
287         // behavior depends both on both the parent and the inline block's directionality, we
288         // must keep track of these separately.
289         bool ltr = isLeftToRightDirection();
290         if (ltr != flowIsLTR) {
291           // Width in pixels of the visible portion of the box, excluding the ellipsis.
292           int visibleBoxWidth = visibleRightEdge - visibleLeftEdge  - ellipsisWidth;
293           ellipsisX = ltr ? left() + visibleBoxWidth : right() - visibleBoxWidth;
294         }
295
296         int offset = offsetForPosition(ellipsisX, false);
297         if (offset == 0) {
298             // No characters should be rendered.  Set ourselves to full truncation and place the ellipsis at the min of our start
299             // and the ellipsis edge.
300             m_truncation = cFullTruncation;
301             truncatedWidth += ellipsisWidth;
302             return flowIsLTR ? std::min(ellipsisX, x()) : std::max(ellipsisX, right() - ellipsisWidth);
303         }
304
305         // Set the truncation index on the text run.
306         m_truncation = offset;
307
308         // If we got here that means that we were only partially truncated and we need to return the pixel offset at which
309         // to place the ellipsis.
310         float widthOfVisibleText = renderer().width(m_start, offset, textPos(), isFirstLine());
311
312         // The ellipsis needs to be placed just after the last visible character.
313         // Where "after" is defined by the flow directionality, not the inline
314         // box directionality.
315         // e.g. In the case of an LTR inline box truncated in an RTL flow then we can
316         // have a situation such as |Hello| -> |...He|
317         truncatedWidth += widthOfVisibleText + ellipsisWidth;
318         if (flowIsLTR)
319             return left() + widthOfVisibleText;
320         else
321             return right() - widthOfVisibleText - ellipsisWidth;
322     }
323     truncatedWidth += logicalWidth();
324     return -1;
325 }
326
327
328
329 bool InlineTextBox::isLineBreak() const
330 {
331     return renderer().style().preserveNewline() && len() == 1 && renderer().text()[start()] == '\n';
332 }
333
334 bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/,
335     HitTestAction /*hitTestAction*/)
336 {
337     if (!visibleToHitTesting())
338         return false;
339
340     if (isLineBreak())
341         return false;
342
343     if (m_truncation == cFullTruncation)
344         return false;
345
346     FloatRect rect(locationIncludingFlipping(), size());
347     // Make sure truncated text is ignored while hittesting.
348     if (m_truncation != cNoTruncation) {
349         LayoutUnit widthOfVisibleText = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
350
351         if (isHorizontal())
352             renderer().style().isLeftToRightDirection() ? rect.setWidth(widthOfVisibleText) : rect.shiftXEdgeTo(right() - widthOfVisibleText);
353         else
354             rect.setHeight(widthOfVisibleText);
355     }
356
357     rect.moveBy(accumulatedOffset);
358
359     if (locationInContainer.intersects(rect)) {
360         renderer().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset)));
361         if (result.addNodeToListBasedTestResult(renderer().textNode(), request, locationInContainer, rect) == HitTestProgress::Stop)
362             return true;
363     }
364     return false;
365 }
366
367 std::optional<bool> InlineTextBox::emphasisMarkExistsAndIsAbove(const RenderStyle& style) const
368 {
369     // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
370     if (style.textEmphasisMark() == TextEmphasisMark::None)
371         return std::nullopt;
372
373     const OptionSet<TextEmphasisPosition> horizontalMask { TextEmphasisPosition::Left, TextEmphasisPosition::Right };
374
375     auto emphasisPosition = style.textEmphasisPosition();
376     auto emphasisPositionHorizontalValue = emphasisPosition & horizontalMask;
377     ASSERT(!((emphasisPosition & TextEmphasisPosition::Over) && (emphasisPosition & TextEmphasisPosition::Under)));
378     ASSERT(emphasisPositionHorizontalValue != horizontalMask);
379
380     bool isAbove = false;
381     if (!emphasisPositionHorizontalValue)
382         isAbove = emphasisPosition.contains(TextEmphasisPosition::Over);
383     else if (style.isHorizontalWritingMode())
384         isAbove = emphasisPosition.contains(TextEmphasisPosition::Over);
385     else
386         isAbove = emphasisPositionHorizontalValue == TextEmphasisPosition::Right;
387
388     if ((style.isHorizontalWritingMode() && (emphasisPosition & TextEmphasisPosition::Under))
389         || (!style.isHorizontalWritingMode() && (emphasisPosition & TextEmphasisPosition::Left)))
390         return isAbove; // Ruby text is always over, so it cannot suppress emphasis marks under.
391
392     RenderBlock* containingBlock = renderer().containingBlock();
393     if (!containingBlock->isRubyBase())
394         return isAbove; // This text is not inside a ruby base, so it does not have ruby text over it.
395
396     if (!is<RenderRubyRun>(*containingBlock->parent()))
397         return isAbove; // Cannot get the ruby text.
398
399     RenderRubyText* rubyText = downcast<RenderRubyRun>(*containingBlock->parent()).rubyText();
400
401     // The emphasis marks over are suppressed only if there is a ruby text box and it not empty.
402     if (rubyText && rubyText->hasLines())
403         return std::nullopt;
404
405     return isAbove;
406 }
407
408 struct InlineTextBox::MarkedTextStyle {
409     static bool areBackgroundMarkedTextStylesEqual(const MarkedTextStyle& a, const MarkedTextStyle& b)
410     {
411         return a.backgroundColor == b.backgroundColor;
412     }
413     static bool areForegroundMarkedTextStylesEqual(const MarkedTextStyle& a, const MarkedTextStyle& b)
414     {
415         return a.textStyles == b.textStyles && a.textShadow == b.textShadow && a.alpha == b.alpha;
416     }
417     static bool areDecorationMarkedTextStylesEqual(const MarkedTextStyle& a, const MarkedTextStyle& b)
418     {
419         return a.textDecorationStyles == b.textDecorationStyles && a.textShadow == b.textShadow && a.alpha == b.alpha;
420     }
421
422     Color backgroundColor;
423     TextPaintStyle textStyles;
424     TextDecorationPainter::Styles textDecorationStyles;
425     std::optional<ShadowData> textShadow;
426     float alpha;
427 };
428
429 struct InlineTextBox::StyledMarkedText : MarkedText {
430     StyledMarkedText(const MarkedText& marker)
431         : MarkedText { marker }
432     {
433     }
434
435     MarkedTextStyle style;
436 };
437
438 static MarkedText createMarkedTextFromSelectionInBox(const InlineTextBox& box)
439 {
440     unsigned selectionStart;
441     unsigned selectionEnd;
442     std::tie(selectionStart, selectionEnd) = box.selectionStartEnd();
443     if (selectionStart < selectionEnd)
444         return { selectionStart, selectionEnd, MarkedText::Selection };
445     return { };
446 }
447
448 void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/)
449 {
450     if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer().style().visibility() != Visibility::Visible
451         || m_truncation == cFullTruncation || paintInfo.phase == PaintPhase::Outline || !hasTextContent())
452         return;
453
454     ASSERT(paintInfo.phase != PaintPhase::SelfOutline && paintInfo.phase != PaintPhase::ChildOutlines);
455
456     LayoutUnit logicalLeftSide = logicalLeftVisualOverflow();
457     LayoutUnit logicalRightSide = logicalRightVisualOverflow();
458     LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y());
459     LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide;
460     
461     LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
462     LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
463     
464     FloatPoint localPaintOffset(paintOffset);
465     
466     if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
467         return;
468
469     bool isPrinting = renderer().document().printing();
470     
471     // Determine whether or not we're selected.
472     bool haveSelection = !isPrinting && paintInfo.phase != PaintPhase::TextClip && selectionState() != RenderObject::SelectionNone;
473     if (!haveSelection && paintInfo.phase == PaintPhase::Selection) {
474         // When only painting the selection, don't bother to paint if there is none.
475         return;
476     }
477
478     if (m_truncation != cNoTruncation) {
479         if (renderer().containingBlock()->style().isLeftToRightDirection() != isLeftToRightDirection()) {
480             // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
481             // at which we start drawing text.
482             // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
483             // |Hello|CBA| -> |...He|CBA|
484             // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
485             // farther to the right.
486             // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
487             // truncated string i.e.  |Hello|CBA| -> |...lo|CBA|
488             LayoutUnit widthOfVisibleText = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
489             LayoutUnit widthOfHiddenText = logicalWidth() - widthOfVisibleText;
490             LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0);
491             localPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
492         }
493     }
494
495     GraphicsContext& context = paintInfo.context();
496
497     const RenderStyle& lineStyle = this->lineStyle();
498     
499     localPaintOffset.move(0, lineStyle.isHorizontalWritingMode() ? 0 : -logicalHeight());
500
501     FloatPoint boxOrigin = locationIncludingFlipping();
502     boxOrigin.moveBy(localPaintOffset);
503     FloatRect boxRect(boxOrigin, FloatSize(logicalWidth(), logicalHeight()));
504
505     auto* combinedText = this->combinedText();
506
507     bool shouldRotate = !isHorizontal() && !combinedText;
508     if (shouldRotate)
509         context.concatCTM(rotation(boxRect, Clockwise));
510
511     // Determine whether or not we have composition underlines to draw.
512     bool containsComposition = renderer().textNode() && renderer().frame().editor().compositionNode() == renderer().textNode();
513     bool useCustomUnderlines = containsComposition && renderer().frame().editor().compositionUsesCustomUnderlines();
514
515     MarkedTextStyle unmarkedStyle = computeStyleForUnmarkedMarkedText(paintInfo);
516
517     // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
518     // and composition underlines.
519     if (paintInfo.phase != PaintPhase::Selection && paintInfo.phase != PaintPhase::TextClip && !isPrinting) {
520         if (containsComposition && !useCustomUnderlines)
521             paintCompositionBackground(paintInfo, boxOrigin);
522
523         Vector<MarkedText> markedTexts = collectMarkedTextsForDocumentMarkers(TextPaintPhase::Background);
524 #if ENABLE(TEXT_SELECTION)
525         if (haveSelection && !useCustomUnderlines && !context.paintingDisabled()) {
526             auto selectionMarkedText = createMarkedTextFromSelectionInBox(*this);
527             if (!selectionMarkedText.isEmpty())
528                 markedTexts.append(WTFMove(selectionMarkedText));
529         }
530 #endif
531         auto styledMarkedTexts = subdivideAndResolveStyle(markedTexts, unmarkedStyle, paintInfo);
532
533         // Coalesce styles of adjacent marked texts to minimize the number of drawing commands.
534         auto coalescedStyledMarkedTexts = coalesceAdjacentMarkedTexts(styledMarkedTexts, &MarkedTextStyle::areBackgroundMarkedTextStylesEqual);
535
536         paintMarkedTexts(paintInfo, TextPaintPhase::Background, boxRect, coalescedStyledMarkedTexts);
537     }
538
539     // FIXME: Right now, InlineTextBoxes never call addRelevantUnpaintedObject() even though they might
540     // legitimately be unpainted if they are waiting on a slow-loading web font. We should fix that, and
541     // when we do, we will have to account for the fact the InlineTextBoxes do not always have unique
542     // renderers and Page currently relies on each unpainted object having a unique renderer.
543     if (paintInfo.phase == PaintPhase::Foreground)
544         renderer().page().addRelevantRepaintedObject(&renderer(), IntRect(boxOrigin.x(), boxOrigin.y(), logicalWidth(), logicalHeight()));
545
546     if (paintInfo.phase == PaintPhase::Foreground)
547         paintPlatformDocumentMarkers(context, boxOrigin);
548
549     // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
550     bool shouldPaintSelectionForeground = haveSelection && !useCustomUnderlines;
551     Vector<MarkedText> markedTexts;
552     if (paintInfo.phase != PaintPhase::Selection) {
553         // The marked texts for the gaps between document markers and selection are implicitly created by subdividing the entire line.
554         markedTexts.append({ clampedOffset(m_start), clampedOffset(end() + 1), MarkedText::Unmarked });
555         if (!isPrinting) {
556             markedTexts.appendVector(collectMarkedTextsForDocumentMarkers(TextPaintPhase::Foreground));
557
558             bool shouldPaintDraggedContent = !(paintInfo.paintBehavior.contains(PaintBehavior::ExcludeSelection));
559             if (shouldPaintDraggedContent) {
560                 auto markedTextsForDraggedContent = collectMarkedTextsForDraggedContent();
561                 if (!markedTextsForDraggedContent.isEmpty()) {
562                     shouldPaintSelectionForeground = false;
563                     markedTexts.appendVector(markedTextsForDraggedContent);
564                 }
565             }
566         }
567     }
568     // The selection marked text acts as a placeholder when computing the marked texts for the gaps...
569     if (shouldPaintSelectionForeground) {
570         ASSERT(!isPrinting);
571         auto selectionMarkedText = createMarkedTextFromSelectionInBox(*this);
572         if (!selectionMarkedText.isEmpty())
573             markedTexts.append(WTFMove(selectionMarkedText));
574     }
575
576     auto styledMarkedTexts = subdivideAndResolveStyle(markedTexts, unmarkedStyle, paintInfo);
577
578     // ... now remove the selection marked text if we are excluding selection.
579     if (!isPrinting && paintInfo.paintBehavior.contains(PaintBehavior::ExcludeSelection))
580         styledMarkedTexts.removeAllMatching([] (const StyledMarkedText& markedText) { return markedText.type == MarkedText::Selection; });
581
582     // Coalesce styles of adjacent marked texts to minimize the number of drawing commands.
583     auto coalescedStyledMarkedTexts = coalesceAdjacentMarkedTexts(styledMarkedTexts, &MarkedTextStyle::areForegroundMarkedTextStylesEqual);
584
585     paintMarkedTexts(paintInfo, TextPaintPhase::Foreground, boxRect, coalescedStyledMarkedTexts);
586
587     // Paint decorations
588     auto textDecorations = lineStyle.textDecorationsInEffect();
589     if (!textDecorations.isEmpty() && paintInfo.phase != PaintPhase::Selection) {
590         TextRun textRun = createTextRun();
591         unsigned length = textRun.length();
592         if (m_truncation != cNoTruncation)
593             length = m_truncation;
594         unsigned selectionStart = 0;
595         unsigned selectionEnd = 0;
596         if (haveSelection)
597             std::tie(selectionStart, selectionEnd) = selectionStartEnd();
598
599         FloatRect textDecorationSelectionClipOutRect;
600         if ((paintInfo.paintBehavior.contains(PaintBehavior::ExcludeSelection)) && selectionStart < selectionEnd && selectionEnd <= length) {
601             textDecorationSelectionClipOutRect = logicalOverflowRect();
602             textDecorationSelectionClipOutRect.moveBy(localPaintOffset);
603             float logicalWidthBeforeRange;
604             float logicalWidthAfterRange;
605             float logicalSelectionWidth = lineFont().widthOfTextRange(textRun, selectionStart, selectionEnd, nullptr, &logicalWidthBeforeRange, &logicalWidthAfterRange);
606             // FIXME: Do we need to handle vertical bottom to top text?
607             if (!isHorizontal()) {
608                 textDecorationSelectionClipOutRect.move(0, logicalWidthBeforeRange);
609                 textDecorationSelectionClipOutRect.setHeight(logicalSelectionWidth);
610             } else if (direction() == TextDirection::RTL) {
611                 textDecorationSelectionClipOutRect.move(logicalWidthAfterRange, 0);
612                 textDecorationSelectionClipOutRect.setWidth(logicalSelectionWidth);
613             } else {
614                 textDecorationSelectionClipOutRect.move(logicalWidthBeforeRange, 0);
615                 textDecorationSelectionClipOutRect.setWidth(logicalSelectionWidth);
616             }
617         }
618
619         // Coalesce styles of adjacent marked texts to minimize the number of drawing commands.
620         auto coalescedStyledMarkedTexts = coalesceAdjacentMarkedTexts(styledMarkedTexts, &MarkedTextStyle::areDecorationMarkedTextStylesEqual);
621
622         paintMarkedTexts(paintInfo, TextPaintPhase::Decoration, boxRect, coalescedStyledMarkedTexts, textDecorationSelectionClipOutRect);
623     }
624
625     // 3. Paint fancy decorations, including composition underlines and platform-specific underlines for spelling errors, grammar errors, et cetera.
626     if (paintInfo.phase == PaintPhase::Foreground && useCustomUnderlines)
627         paintCompositionUnderlines(paintInfo, boxOrigin);
628     
629     if (shouldRotate)
630         context.concatCTM(rotation(boxRect, Counterclockwise));
631 }
632
633 unsigned InlineTextBox::clampedOffset(unsigned x) const
634 {
635     unsigned offset = std::max(std::min(x, m_start + m_len), m_start) - m_start;
636     if (m_truncation == cFullTruncation)
637         return offset;
638     if (m_truncation != cNoTruncation)
639         offset = std::min<unsigned>(offset, m_truncation);
640     else if (offset == m_len) {
641         // Fix up the offset if we are combined text or have a hyphen because we manage these embellishments.
642         // That is, they are not reflected in renderer().text(). We treat combined text as a single unit.
643         // We also treat the last codepoint in this box and the hyphen as a single unit.
644         if (auto* combinedText = this->combinedText())
645             offset = combinedText->combinedStringForRendering().length();
646         else if (hasHyphen())
647             offset += lineStyle().hyphenString().length();
648     }
649     return offset;
650 }
651
652 std::pair<unsigned, unsigned> InlineTextBox::selectionStartEnd() const
653 {
654     auto selectionState = renderer().selectionState();
655     if (selectionState == RenderObject::SelectionInside)
656         return { 0, clampedOffset(m_start + m_len) };
657     
658     auto start = renderer().view().selection().startPosition();
659     auto end = renderer().view().selection().endPosition();
660     if (selectionState == RenderObject::SelectionStart)
661         end = renderer().text().length();
662     else if (selectionState == RenderObject::SelectionEnd)
663         start = 0;
664     return { clampedOffset(start), clampedOffset(end) };
665 }
666
667 bool InlineTextBox::hasMarkers() const
668 {
669     return collectMarkedTextsForDocumentMarkers(TextPaintPhase::Decoration).size();
670 }
671
672 void InlineTextBox::paintPlatformDocumentMarkers(GraphicsContext& context, const FloatPoint& boxOrigin)
673 {
674     // This must match calculateUnionOfAllDocumentMarkerBounds().
675     for (auto& markedText : subdivide(collectMarkedTextsForDocumentMarkers(TextPaintPhase::Decoration), OverlapStrategy::Frontmost))
676         paintPlatformDocumentMarker(context, boxOrigin, markedText);
677 }
678
679 FloatRect InlineTextBox::calculateUnionOfAllDocumentMarkerBounds() const
680 {
681     // This must match paintPlatformDocumentMarkers().
682     FloatRect result;
683     for (auto& markedText : subdivide(collectMarkedTextsForDocumentMarkers(TextPaintPhase::Decoration), OverlapStrategy::Frontmost))
684         result = unionRect(result, calculateDocumentMarkerBounds(markedText));
685     return result;
686 }
687
688 FloatRect InlineTextBox::calculateDocumentMarkerBounds(const MarkedText& markedText) const
689 {
690     auto& font = lineFont();
691     auto ascent = font.fontMetrics().ascent();
692     auto fontSize = std::min(std::max(font.size(), 10.0f), 40.0f);
693     auto y = ascent + 0.11035 * fontSize;
694     auto height = 0.13247 * fontSize;
695
696     // Avoid measuring the text when the entire line box is selected as an optimization.
697     if (markedText.startOffset || markedText.endOffset != clampedOffset(end() + 1)) {
698         TextRun run = createTextRun();
699         LayoutRect selectionRect = LayoutRect(0, y, 0, height);
700         lineFont().adjustSelectionRectForText(run, selectionRect, markedText.startOffset, markedText.endOffset);
701         return selectionRect;
702     }
703
704     return FloatRect(0, y, logicalWidth(), height);
705 }
706
707 void InlineTextBox::paintPlatformDocumentMarker(GraphicsContext& context, const FloatPoint& boxOrigin, const MarkedText& markedText)
708 {
709     // Never print spelling/grammar markers (5327887)
710     if (renderer().document().printing())
711         return;
712
713     if (m_truncation == cFullTruncation)
714         return;
715
716     auto bounds = calculateDocumentMarkerBounds(markedText);
717
718     auto lineStyleForMarkedTextType = [&]() -> DocumentMarkerLineStyle {
719         bool shouldUseDarkAppearance = renderer().document().useDarkAppearance();
720         switch (markedText.type) {
721         case MarkedText::SpellingError:
722             return { DocumentMarkerLineStyle::Mode::Spelling, shouldUseDarkAppearance };
723         case MarkedText::GrammarError:
724             return { DocumentMarkerLineStyle::Mode::Grammar, shouldUseDarkAppearance };
725         case MarkedText::Correction:
726             return { DocumentMarkerLineStyle::Mode::AutocorrectionReplacement, shouldUseDarkAppearance };
727         case MarkedText::DictationAlternatives:
728             return { DocumentMarkerLineStyle::Mode::DictationAlternatives, shouldUseDarkAppearance };
729 #if PLATFORM(IOS_FAMILY)
730         case MarkedText::DictationPhraseWithAlternatives:
731             // FIXME: Rename DocumentMarkerLineStyle::TextCheckingDictationPhraseWithAlternatives and remove the PLATFORM(IOS_FAMILY)-guard.
732             return { DocumentMarkerLineStyle::Mode::TextCheckingDictationPhraseWithAlternatives, shouldUseDarkAppearance };
733 #endif
734         default:
735             ASSERT_NOT_REACHED();
736             return { DocumentMarkerLineStyle::Mode::Spelling, shouldUseDarkAppearance };
737         }
738     };
739
740     bounds.moveBy(boxOrigin);
741     context.drawDotsForDocumentMarker(bounds, lineStyleForMarkedTextType());
742 }
743
744 auto InlineTextBox::computeStyleForUnmarkedMarkedText(const PaintInfo& paintInfo) const -> MarkedTextStyle
745 {
746     auto& lineStyle = this->lineStyle();
747
748     MarkedTextStyle style;
749     style.textDecorationStyles = TextDecorationPainter::stylesForRenderer(renderer(), lineStyle.textDecorationsInEffect(), isFirstLine());
750     style.textStyles = computeTextPaintStyle(renderer().frame(), lineStyle, paintInfo);
751     style.textShadow = ShadowData::clone(paintInfo.forceTextColor() ? nullptr : lineStyle.textShadow());
752     style.alpha = 1;
753     return style;
754 }
755
756 auto InlineTextBox::resolveStyleForMarkedText(const MarkedText& markedText, const MarkedTextStyle& baseStyle, const PaintInfo& paintInfo) -> StyledMarkedText
757 {
758     MarkedTextStyle style = baseStyle;
759     switch (markedText.type) {
760     case MarkedText::Correction:
761     case MarkedText::DictationAlternatives:
762 #if PLATFORM(IOS_FAMILY)
763     // FIXME: See <rdar://problem/8933352>. Also, remove the PLATFORM(IOS_FAMILY)-guard.
764     case MarkedText::DictationPhraseWithAlternatives:
765 #endif
766     case MarkedText::GrammarError:
767     case MarkedText::SpellingError:
768     case MarkedText::Unmarked:
769         break;
770     case MarkedText::DraggedContent:
771         style.alpha = 0.25;
772         break;
773     case MarkedText::Selection: {
774         style.textStyles = computeTextSelectionPaintStyle(style.textStyles, renderer(), lineStyle(), paintInfo, style.textShadow);
775
776         Color selectionBackgroundColor = renderer().selectionBackgroundColor();
777         style.backgroundColor = selectionBackgroundColor;
778         if (selectionBackgroundColor.isValid() && selectionBackgroundColor.alpha() && style.textStyles.fillColor == selectionBackgroundColor)
779             style.backgroundColor = { 0xff - selectionBackgroundColor.red(), 0xff - selectionBackgroundColor.green(), 0xff - selectionBackgroundColor.blue() };
780         break;
781     }
782     case MarkedText::TextMatch: {
783         // Text matches always use the light system appearance.
784         OptionSet<StyleColor::Options> styleColorOptions = { StyleColor::Options::UseSystemAppearance };
785 #if PLATFORM(MAC)
786         style.textStyles.fillColor = renderer().theme().systemColor(CSSValueAppleSystemLabel, styleColorOptions);
787 #endif
788         style.backgroundColor = markedText.marker->isActiveMatch() ? renderer().theme().activeTextSearchHighlightColor(styleColorOptions) : renderer().theme().inactiveTextSearchHighlightColor(styleColorOptions);
789         break;
790     }
791     }
792     StyledMarkedText styledMarkedText = markedText;
793     styledMarkedText.style = WTFMove(style);
794     return styledMarkedText;
795 }
796
797 auto InlineTextBox::subdivideAndResolveStyle(const Vector<MarkedText>& textsToSubdivide, const MarkedTextStyle& baseStyle, const PaintInfo& paintInfo) -> Vector<StyledMarkedText>
798 {
799     if (textsToSubdivide.isEmpty())
800         return { };
801
802     auto markedTexts = subdivide(textsToSubdivide);
803     ASSERT(!markedTexts.isEmpty());
804     if (UNLIKELY(markedTexts.isEmpty()))
805         return { };
806
807     // Compute frontmost overlapping styled marked texts.
808     Vector<StyledMarkedText> frontmostMarkedTexts;
809     frontmostMarkedTexts.reserveInitialCapacity(markedTexts.size());
810     frontmostMarkedTexts.uncheckedAppend(resolveStyleForMarkedText(markedTexts[0], baseStyle, paintInfo));
811     for (auto it = markedTexts.begin() + 1, end = markedTexts.end(); it != end; ++it) {
812         StyledMarkedText& previousStyledMarkedText = frontmostMarkedTexts.last();
813         if (previousStyledMarkedText.startOffset == it->startOffset && previousStyledMarkedText.endOffset == it->endOffset) {
814             // Marked texts completely cover each other.
815             previousStyledMarkedText = resolveStyleForMarkedText(*it, previousStyledMarkedText.style, paintInfo);
816             continue;
817         }
818         frontmostMarkedTexts.uncheckedAppend(resolveStyleForMarkedText(*it, baseStyle, paintInfo));
819     }
820
821     return frontmostMarkedTexts;
822 }
823
824 auto InlineTextBox::coalesceAdjacentMarkedTexts(const Vector<StyledMarkedText>& textsToCoalesce, MarkedTextStylesEqualityFunction areMarkedTextStylesEqual) -> Vector<StyledMarkedText>
825 {
826     if (textsToCoalesce.isEmpty())
827         return { };
828
829     auto areAdjacentMarkedTextsWithSameStyle = [&] (const StyledMarkedText& a, const StyledMarkedText& b) {
830         return a.endOffset == b.startOffset && areMarkedTextStylesEqual(a.style, b.style);
831     };
832
833     Vector<StyledMarkedText> styledMarkedTexts;
834     styledMarkedTexts.reserveInitialCapacity(textsToCoalesce.size());
835     styledMarkedTexts.uncheckedAppend(textsToCoalesce[0]);
836     for (auto it = textsToCoalesce.begin() + 1, end = textsToCoalesce.end(); it != end; ++it) {
837         StyledMarkedText& previousStyledMarkedText = styledMarkedTexts.last();
838         if (areAdjacentMarkedTextsWithSameStyle(previousStyledMarkedText, *it)) {
839             previousStyledMarkedText.endOffset = it->endOffset;
840             continue;
841         }
842         styledMarkedTexts.uncheckedAppend(*it);
843     }
844
845     return styledMarkedTexts;
846 }
847
848 Vector<MarkedText> InlineTextBox::collectMarkedTextsForDraggedContent()
849 {
850     using DraggendContentRange = std::pair<unsigned, unsigned>;
851     auto draggedContentRanges = renderer().draggedContentRangesBetweenOffsets(m_start, m_start + m_len);
852     Vector<MarkedText> result = draggedContentRanges.map([this] (const DraggendContentRange& range) -> MarkedText {
853         return { clampedOffset(range.first), clampedOffset(range.second), MarkedText::DraggedContent };
854     });
855     return result;
856 }
857
858 Vector<MarkedText> InlineTextBox::collectMarkedTextsForDocumentMarkers(TextPaintPhase phase) const
859 {
860     ASSERT_ARG(phase, phase == TextPaintPhase::Background || phase == TextPaintPhase::Foreground || phase == TextPaintPhase::Decoration);
861
862     if (!renderer().textNode())
863         return { };
864
865     Vector<RenderedDocumentMarker*> markers = renderer().document().markers().markersFor(renderer().textNode());
866
867     auto markedTextTypeForMarkerType = [] (DocumentMarker::MarkerType type) {
868         switch (type) {
869         case DocumentMarker::Spelling:
870             return MarkedText::SpellingError;
871         case DocumentMarker::Grammar:
872             return MarkedText::GrammarError;
873         case DocumentMarker::CorrectionIndicator:
874             return MarkedText::Correction;
875         case DocumentMarker::TextMatch:
876             return MarkedText::TextMatch;
877         case DocumentMarker::DictationAlternatives:
878             return MarkedText::DictationAlternatives;
879 #if PLATFORM(IOS_FAMILY)
880         case DocumentMarker::DictationPhraseWithAlternatives:
881             return MarkedText::DictationPhraseWithAlternatives;
882 #endif
883         default:
884             return MarkedText::Unmarked;
885         }
886     };
887
888     Vector<MarkedText> markedTexts;
889     markedTexts.reserveInitialCapacity(markers.size());
890
891     // Give any document markers that touch this run a chance to draw before the text has been drawn.
892     // Note end() points at the last char, not one past it like endOffset and ranges do.
893     for (auto* marker : markers) {
894         // Collect either the background markers or the foreground markers, but not both
895         switch (marker->type()) {
896         case DocumentMarker::Grammar:
897         case DocumentMarker::Spelling:
898         case DocumentMarker::CorrectionIndicator:
899         case DocumentMarker::Replacement:
900         case DocumentMarker::DictationAlternatives:
901 #if PLATFORM(IOS_FAMILY)
902         // FIXME: Remove the PLATFORM(IOS_FAMILY)-guard.
903         case DocumentMarker::DictationPhraseWithAlternatives:
904 #endif
905             if (phase != TextPaintPhase::Decoration)
906                 continue;
907             break;
908         case DocumentMarker::TextMatch:
909             if (!renderer().frame().editor().markedTextMatchesAreHighlighted())
910                 continue;
911             if (phase == TextPaintPhase::Decoration)
912                 continue;
913             break;
914 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
915         case DocumentMarker::TelephoneNumber:
916             if (!renderer().frame().editor().markedTextMatchesAreHighlighted())
917                 continue;
918             if (phase != TextPaintPhase::Background)
919                 continue;
920             break;
921 #endif
922         default:
923             continue;
924         }
925
926         if (marker->endOffset() <= start()) {
927             // Marker is completely before this run. This might be a marker that sits before the
928             // first run we draw, or markers that were within runs we skipped due to truncation.
929             continue;
930         }
931
932         if (marker->startOffset() > end()) {
933             // Marker is completely after this run, bail. A later run will paint it.
934             break;
935         }
936
937         // Marker intersects this run. Collect it.
938         switch (marker->type()) {
939         case DocumentMarker::Spelling:
940         case DocumentMarker::CorrectionIndicator:
941         case DocumentMarker::DictationAlternatives:
942         case DocumentMarker::Grammar:
943 #if PLATFORM(IOS_FAMILY)
944         // FIXME: See <rdar://problem/8933352>. Also, remove the PLATFORM(IOS_FAMILY)-guard.
945         case DocumentMarker::DictationPhraseWithAlternatives:
946 #endif
947         case DocumentMarker::TextMatch:
948             markedTexts.uncheckedAppend({ clampedOffset(marker->startOffset()), clampedOffset(marker->endOffset()), markedTextTypeForMarkerType(marker->type()), marker });
949             break;
950         case DocumentMarker::Replacement:
951             break;
952 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
953         case DocumentMarker::TelephoneNumber:
954             break;
955 #endif
956         default:
957             ASSERT_NOT_REACHED();
958         }
959     }
960     return markedTexts;
961 }
962
963 FloatPoint InlineTextBox::textOriginFromBoxRect(const FloatRect& boxRect) const
964 {
965     FloatPoint textOrigin { boxRect.x(), boxRect.y() + lineFont().fontMetrics().ascent() };
966     if (auto* combinedText = this->combinedText()) {
967         if (auto newOrigin = combinedText->computeTextOrigin(boxRect))
968             textOrigin = newOrigin.value();
969     }
970     if (isHorizontal())
971         textOrigin.setY(roundToDevicePixel(LayoutUnit { textOrigin.y() }, renderer().document().deviceScaleFactor()));
972     else
973         textOrigin.setX(roundToDevicePixel(LayoutUnit { textOrigin.x() }, renderer().document().deviceScaleFactor()));
974     return textOrigin;
975 }
976
977 void InlineTextBox::paintMarkedTexts(PaintInfo& paintInfo, TextPaintPhase phase, const FloatRect& boxRect, const Vector<StyledMarkedText>& markedTexts, const FloatRect& decorationClipOutRect)
978 {
979     switch (phase) {
980     case TextPaintPhase::Background:
981         for (auto& markedText : markedTexts)
982             paintMarkedTextBackground(paintInfo, boxRect.location(), markedText.style.backgroundColor, markedText.startOffset, markedText.endOffset);
983         return;
984     case TextPaintPhase::Foreground:
985         for (auto& markedText : markedTexts)
986             paintMarkedTextForeground(paintInfo, boxRect, markedText);
987         return;
988     case TextPaintPhase::Decoration:
989         for (auto& markedText : markedTexts)
990             paintMarkedTextDecoration(paintInfo, boxRect, decorationClipOutRect, markedText);
991         return;
992     }
993 }
994
995 void InlineTextBox::paintMarkedTextBackground(PaintInfo& paintInfo, const FloatPoint& boxOrigin, const Color& color, unsigned clampedStartOffset, unsigned clampedEndOffset)
996 {
997     if (clampedStartOffset >= clampedEndOffset)
998         return;
999
1000     GraphicsContext& context = paintInfo.context();
1001     GraphicsContextStateSaver stateSaver { context };
1002     updateGraphicsContext(context, TextPaintStyle { color }); // Don't draw text at all!
1003
1004     // Note that if the text is truncated, we let the thing being painted in the truncation
1005     // draw its own highlight.
1006     TextRun textRun = createTextRun();
1007
1008     const RootInlineBox& rootBox = root();
1009     LayoutUnit selectionBottom = rootBox.selectionBottom();
1010     LayoutUnit selectionTop = rootBox.selectionTopAdjustedForPrecedingBlock();
1011
1012     // Use same y positioning and height as for selection, so that when the selection and this subrange are on
1013     // the same word there are no pieces sticking out.
1014     LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop;
1015     LayoutUnit selectionHeight = std::max<LayoutUnit>(0, selectionBottom - selectionTop);
1016
1017     LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, logicalWidth(), selectionHeight);
1018     lineFont().adjustSelectionRectForText(textRun, selectionRect, clampedStartOffset, clampedEndOffset);
1019
1020     // FIXME: Support painting combined text. See <https://bugs.webkit.org/show_bug.cgi?id=180993>.
1021     context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), color);
1022 }
1023
1024 void InlineTextBox::paintMarkedTextForeground(PaintInfo& paintInfo, const FloatRect& boxRect, const StyledMarkedText& markedText)
1025 {
1026     if (markedText.startOffset >= markedText.endOffset)
1027         return;
1028
1029     GraphicsContext& context = paintInfo.context();
1030     const FontCascade& font = lineFont();
1031     const RenderStyle& lineStyle = this->lineStyle();
1032
1033     float emphasisMarkOffset = 0;
1034     std::optional<bool> markExistsAndIsAbove = emphasisMarkExistsAndIsAbove(lineStyle);
1035     const AtomicString& emphasisMark = markExistsAndIsAbove ? lineStyle.textEmphasisMarkString() : nullAtom();
1036     if (!emphasisMark.isEmpty())
1037         emphasisMarkOffset = *markExistsAndIsAbove ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
1038
1039     TextPainter textPainter { context };
1040     textPainter.setFont(font);
1041     textPainter.setStyle(markedText.style.textStyles);
1042     textPainter.setIsHorizontal(isHorizontal());
1043     if (markedText.style.textShadow) {
1044         textPainter.setShadow(&markedText.style.textShadow.value());
1045         if (lineStyle.hasAppleColorFilter())
1046             textPainter.setShadowColorFilter(&lineStyle.appleColorFilter());
1047     }
1048     textPainter.setEmphasisMark(emphasisMark, emphasisMarkOffset, combinedText());
1049
1050     TextRun textRun = createTextRun();
1051     textPainter.setGlyphDisplayListIfNeeded(*this, paintInfo, font, context, textRun);
1052
1053     GraphicsContextStateSaver stateSaver { context, false };
1054     if (markedText.type == MarkedText::DraggedContent) {
1055         stateSaver.save();
1056         context.setAlpha(markedText.style.alpha);
1057     }
1058     // TextPainter wants the box rectangle and text origin of the entire line box.
1059     textPainter.paintRange(textRun, boxRect, textOriginFromBoxRect(boxRect), markedText.startOffset, markedText.endOffset);
1060 }
1061
1062 void InlineTextBox::paintMarkedTextDecoration(PaintInfo& paintInfo, const FloatRect& boxRect, const FloatRect& clipOutRect, const StyledMarkedText& markedText)
1063 {
1064     if (m_truncation == cFullTruncation)
1065         return;
1066
1067     GraphicsContext& context = paintInfo.context();
1068     updateGraphicsContext(context, markedText.style.textStyles);
1069
1070     bool isCombinedText = combinedText();
1071     if (isCombinedText)
1072         context.concatCTM(rotation(boxRect, Clockwise));
1073
1074     // 1. Compute text selection
1075     unsigned startOffset = markedText.startOffset;
1076     unsigned endOffset = markedText.endOffset;
1077     if (startOffset >= endOffset)
1078         return;
1079
1080     // Note that if the text is truncated, we let the thing being painted in the truncation
1081     // draw its own decoration.
1082     TextRun textRun = createTextRun();
1083
1084     // Avoid measuring the text when the entire line box is selected as an optimization.
1085     FloatRect snappedSelectionRect = boxRect;
1086     if (startOffset || endOffset != textRun.length()) {
1087         LayoutRect selectionRect = { boxRect.x(), boxRect.y(), boxRect.width(), boxRect.height() };
1088         lineFont().adjustSelectionRectForText(textRun, selectionRect, startOffset, endOffset);
1089         snappedSelectionRect = snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr());
1090     }
1091
1092     // 2. Paint
1093     TextDecorationPainter decorationPainter { context, lineStyle().textDecorationsInEffect(), renderer(), isFirstLine(), markedText.style.textDecorationStyles };
1094     decorationPainter.setInlineTextBox(this);
1095     decorationPainter.setFont(lineFont());
1096     decorationPainter.setWidth(snappedSelectionRect.width());
1097     decorationPainter.setBaseline(lineStyle().fontMetrics().ascent());
1098     decorationPainter.setIsHorizontal(isHorizontal());
1099     if (markedText.style.textShadow) {
1100         decorationPainter.setTextShadow(&markedText.style.textShadow.value());
1101         if (lineStyle().hasAppleColorFilter())
1102             decorationPainter.setShadowColorFilter(&lineStyle().appleColorFilter());
1103     }
1104
1105     {
1106         GraphicsContextStateSaver stateSaver { context, false };
1107         bool isDraggedContent = markedText.type == MarkedText::DraggedContent;
1108         if (isDraggedContent || !clipOutRect.isEmpty()) {
1109             stateSaver.save();
1110             if (isDraggedContent)
1111                 context.setAlpha(markedText.style.alpha);
1112             if (!clipOutRect.isEmpty())
1113                 context.clipOut(clipOutRect);
1114         }
1115         decorationPainter.paintTextDecoration(textRun.subRun(startOffset, endOffset - startOffset), textOriginFromBoxRect(snappedSelectionRect), snappedSelectionRect.location());
1116     }
1117
1118     if (isCombinedText)
1119         context.concatCTM(rotation(boxRect, Counterclockwise));
1120 }
1121
1122 void InlineTextBox::paintCompositionBackground(PaintInfo& paintInfo, const FloatPoint& boxOrigin)
1123 {
1124     paintMarkedTextBackground(paintInfo, boxOrigin, Color::compositionFill, clampedOffset(renderer().frame().editor().compositionStart()), clampedOffset(renderer().frame().editor().compositionEnd()));
1125 }
1126
1127 void InlineTextBox::paintCompositionUnderlines(PaintInfo& paintInfo, const FloatPoint& boxOrigin) const
1128 {
1129     if (m_truncation == cFullTruncation)
1130         return;
1131
1132     for (auto& underline : renderer().frame().editor().customCompositionUnderlines()) {
1133         if (underline.endOffset <= m_start) {
1134             // Underline is completely before this run. This might be an underline that sits
1135             // before the first run we draw, or underlines that were within runs we skipped
1136             // due to truncation.
1137             continue;
1138         }
1139
1140         if (underline.startOffset > end())
1141             break; // Underline is completely after this run, bail. A later run will paint it.
1142
1143         // Underline intersects this run. Paint it.
1144         paintCompositionUnderline(paintInfo, boxOrigin, underline);
1145
1146         if (underline.endOffset > end() + 1)
1147             break; // Underline also runs into the next run. Bail now, no more marker advancement.
1148     }
1149 }
1150
1151 static inline void mirrorRTLSegment(float logicalWidth, TextDirection direction, float& start, float width)
1152 {
1153     if (direction == TextDirection::LTR)
1154         return;
1155     start = logicalWidth - width - start;
1156 }
1157
1158 void InlineTextBox::paintCompositionUnderline(PaintInfo& paintInfo, const FloatPoint& boxOrigin, const CompositionUnderline& underline) const
1159 {
1160     if (m_truncation == cFullTruncation)
1161         return;
1162     
1163     float start = 0; // start of line to draw, relative to tx
1164     float width = logicalWidth(); // how much line to draw
1165     bool useWholeWidth = true;
1166     unsigned paintStart = m_start;
1167     unsigned paintEnd = end() + 1; // end points at the last char, not past it
1168     if (paintStart <= underline.startOffset) {
1169         paintStart = underline.startOffset;
1170         useWholeWidth = false;
1171         start = renderer().width(m_start, paintStart - m_start, textPos(), isFirstLine());
1172     }
1173     if (paintEnd != underline.endOffset) {      // end points at the last char, not past it
1174         paintEnd = std::min(paintEnd, (unsigned)underline.endOffset);
1175         useWholeWidth = false;
1176     }
1177     if (m_truncation != cNoTruncation) {
1178         paintEnd = std::min(paintEnd, (unsigned)m_start + m_truncation);
1179         useWholeWidth = false;
1180     }
1181     if (!useWholeWidth) {
1182         width = renderer().width(paintStart, paintEnd - paintStart, textPos() + start, isFirstLine());
1183         mirrorRTLSegment(logicalWidth(), direction(), start, width);
1184     }
1185
1186     // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
1187     // All other marked text underlines are 1px thick.
1188     // If there's not enough space the underline will touch or overlap characters.
1189     int lineThickness = 1;
1190     int baseline = lineStyle().fontMetrics().ascent();
1191     if (underline.thick && logicalHeight() - baseline >= 2)
1192         lineThickness = 2;
1193
1194     // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
1195     // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
1196     start += 1;
1197     width -= 2;
1198
1199     GraphicsContext& context = paintInfo.context();
1200     Color underlineColor = underline.compositionUnderlineColor == CompositionUnderlineColor::TextColor ? renderer().style().visitedDependentColorWithColorFilter(CSSPropertyWebkitTextFillColor) : renderer().style().colorByApplyingColorFilter(underline.color);
1201     context.setStrokeColor(underlineColor);
1202     context.setStrokeThickness(lineThickness);
1203     context.drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, renderer().document().printing());
1204 }
1205
1206 int InlineTextBox::caretMinOffset() const
1207 {
1208     return m_start;
1209 }
1210
1211 int InlineTextBox::caretMaxOffset() const
1212 {
1213     return m_start + m_len;
1214 }
1215
1216 float InlineTextBox::textPos() const
1217 {
1218     // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset
1219     // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width.
1220     if (logicalLeft() == 0)
1221         return 0;
1222     return logicalLeft() - root().logicalLeft();
1223 }
1224
1225 int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs) const
1226 {
1227     if (isLineBreak())
1228         return 0;
1229     if (lineOffset - logicalLeft() > logicalWidth())
1230         return isLeftToRightDirection() ? len() : 0;
1231     if (lineOffset - logicalLeft() < 0)
1232         return isLeftToRightDirection() ? 0 : len();
1233     bool ignoreCombinedText = true;
1234     bool ignoreHyphen = true;
1235     return lineFont().offsetForPosition(createTextRun(ignoreCombinedText, ignoreHyphen), lineOffset - logicalLeft(), includePartialGlyphs);
1236 }
1237
1238 float InlineTextBox::positionForOffset(unsigned offset) const
1239 {
1240     ASSERT(offset >= m_start);
1241     ASSERT(offset <= m_start + len());
1242
1243     if (isLineBreak())
1244         return logicalLeft();
1245
1246     unsigned startOffset;
1247     unsigned endOffset;
1248     if (isLeftToRightDirection()) {
1249         startOffset = 0;
1250         endOffset = clampedOffset(offset);
1251     } else {
1252         startOffset = clampedOffset(offset);
1253         endOffset = m_len;
1254     }
1255
1256     // FIXME: Do we need to add rightBearing here?
1257     LayoutRect selectionRect = LayoutRect(logicalLeft(), 0, 0, 0);
1258     bool ignoreCombinedText = true;
1259     bool ignoreHyphen = true;
1260     TextRun textRun = createTextRun(ignoreCombinedText, ignoreHyphen);
1261     lineFont().adjustSelectionRectForText(textRun, selectionRect, startOffset, endOffset);
1262     return snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()).maxX();
1263 }
1264
1265 TextRun InlineTextBox::createTextRun(bool ignoreCombinedText, bool ignoreHyphen) const
1266 {
1267     const auto& style = lineStyle();
1268     TextRun textRun { text(ignoreCombinedText, ignoreHyphen), textPos(), expansion(), expansionBehavior(), direction(), dirOverride() || style.rtlOrdering() == Order::Visual, !renderer().canUseSimpleFontCodePath() };
1269     textRun.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
1270     return textRun;
1271 }
1272
1273 String InlineTextBox::text(bool ignoreCombinedText, bool ignoreHyphen) const
1274 {
1275     if (auto* combinedText = this->combinedText()) {
1276         if (ignoreCombinedText)
1277             return renderer().text().substring(m_start, m_len);
1278         return combinedText->combinedStringForRendering();
1279     }
1280     if (hasHyphen()) {
1281         if (ignoreHyphen)
1282             return renderer().text().substring(m_start, m_len);
1283         return makeString(StringView(renderer().text()).substring(m_start, m_len), lineStyle().hyphenString());
1284     }
1285     return renderer().text().substring(m_start, m_len);
1286 }
1287
1288 inline const RenderCombineText* InlineTextBox::combinedText() const
1289 {
1290     return lineStyle().hasTextCombine() && is<RenderCombineText>(renderer()) && downcast<RenderCombineText>(renderer()).isCombined() ? &downcast<RenderCombineText>(renderer()) : nullptr;
1291 }
1292
1293 ExpansionBehavior InlineTextBox::expansionBehavior() const
1294 {
1295     ExpansionBehavior leadingBehavior;
1296     if (forceLeadingExpansion())
1297         leadingBehavior = ForceLeadingExpansion;
1298     else if (canHaveLeadingExpansion())
1299         leadingBehavior = AllowLeadingExpansion;
1300     else
1301         leadingBehavior = ForbidLeadingExpansion;
1302
1303     ExpansionBehavior trailingBehavior;
1304     if (forceTrailingExpansion())
1305         trailingBehavior = ForceTrailingExpansion;
1306     else if (expansion() && nextLeafChild() && !nextLeafChild()->isLineBreak())
1307         trailingBehavior = AllowTrailingExpansion;
1308     else
1309         trailingBehavior = ForbidTrailingExpansion;
1310
1311     return leadingBehavior | trailingBehavior;
1312 }
1313
1314 #if ENABLE(TREE_DEBUGGING)
1315
1316 const char* InlineTextBox::boxName() const
1317 {
1318     return "InlineTextBox";
1319 }
1320
1321 void InlineTextBox::outputLineBox(TextStream& stream, bool mark, int depth) const
1322 {
1323     stream << "-------- " << (isDirty() ? "D" : "-") << "-";
1324
1325     int printedCharacters = 0;
1326     if (mark) {
1327         stream << "*";
1328         ++printedCharacters;
1329     }
1330     while (++printedCharacters <= depth * 2)
1331         stream << " ";
1332
1333     String value = renderer().text();
1334     value = value.substring(start(), len());
1335     value.replaceWithLiteral('\\', "\\\\");
1336     value.replaceWithLiteral('\n', "\\n");
1337     stream << boxName() << " " << FloatRect(x(), y(), width(), height()) << " (" << this << ") renderer->(" << &renderer() << ") run(" << start() << ", " << start() + len() << ") \"" << value.utf8().data() << "\"";
1338     stream.nextLine();
1339 }
1340
1341 #endif
1342
1343 } // namespace WebCore