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