9ee2515a6c30f74f0a0023a2f388b5a8d22fc1fe
[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(m_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 static inline bool emphasisPositionHasNeitherLeftNorRight(TextEmphasisPosition emphasisPosition)
357 {
358     return !(emphasisPosition & TextEmphasisPositionLeft) && !(emphasisPosition & TextEmphasisPositionRight);
359 }
360
361 bool InlineTextBox::emphasisMarkExistsAndIsAbove(const RenderStyle& style, bool& above) const
362 {
363     // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
364     if (style.textEmphasisMark() == TextEmphasisMarkNone)
365         return false;
366
367     TextEmphasisPosition emphasisPosition = style.textEmphasisPosition();
368     ASSERT(!((emphasisPosition & TextEmphasisPositionOver) && (emphasisPosition & TextEmphasisPositionUnder)));
369     ASSERT(!((emphasisPosition & TextEmphasisPositionLeft) && (emphasisPosition & TextEmphasisPositionRight)));
370     
371     if (emphasisPositionHasNeitherLeftNorRight(emphasisPosition))
372         above = emphasisPosition & TextEmphasisPositionOver;
373     else if (style.isHorizontalWritingMode())
374         above = emphasisPosition & TextEmphasisPositionOver;
375     else
376         above = emphasisPosition & TextEmphasisPositionRight;
377     
378     if ((style.isHorizontalWritingMode() && (emphasisPosition & TextEmphasisPositionUnder))
379         || (!style.isHorizontalWritingMode() && (emphasisPosition & TextEmphasisPositionLeft)))
380         return true; // Ruby text is always over, so it cannot suppress emphasis marks under.
381
382     RenderBlock* containingBlock = renderer().containingBlock();
383     if (!containingBlock->isRubyBase())
384         return true; // This text is not inside a ruby base, so it does not have ruby text over it.
385
386     if (!is<RenderRubyRun>(*containingBlock->parent()))
387         return true; // Cannot get the ruby text.
388
389     RenderRubyText* rubyText = downcast<RenderRubyRun>(*containingBlock->parent()).rubyText();
390
391     // The emphasis marks over are suppressed only if there is a ruby text box and it not empty.
392     return !rubyText || !rubyText->hasLines();
393 }
394
395 struct InlineTextBox::MarkedTextStyle {
396     static bool areBackgroundMarkedTextStylesEqual(const MarkedTextStyle& a, const MarkedTextStyle& b)
397     {
398         return a.backgroundColor == b.backgroundColor;
399     }
400     static bool areForegroundMarkedTextStylesEqual(const MarkedTextStyle& a, const MarkedTextStyle& b)
401     {
402         return a.textStyles == b.textStyles && a.textShadow == b.textShadow && a.alpha == b.alpha;
403     }
404     static bool areDecorationMarkedTextStylesEqual(const MarkedTextStyle& a, const MarkedTextStyle& b)
405     {
406         return a.textDecorationStyles == b.textDecorationStyles && a.textShadow == b.textShadow;
407     }
408
409     Color backgroundColor;
410     TextPaintStyle textStyles;
411     TextDecorationPainter::Styles textDecorationStyles;
412     std::optional<ShadowData> textShadow;
413     float alpha;
414 };
415
416 struct InlineTextBox::StyledMarkedText : MarkedText {
417     StyledMarkedText(const MarkedText& marker)
418         : MarkedText { marker }
419     {
420     }
421
422     MarkedTextStyle style;
423 };
424
425 static MarkedText createMarkedTextFromSelectionInBox(const InlineTextBox& box)
426 {
427     unsigned selectionStart;
428     unsigned selectionEnd;
429     std::tie(selectionStart, selectionEnd) = box.selectionStartEnd();
430     if (selectionStart < selectionEnd)
431         return { selectionStart, selectionEnd, MarkedText::Selection };
432     return { };
433 }
434
435 void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/)
436 {
437     if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer().style().visibility() != VISIBLE
438         || m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len)
439         return;
440
441     ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
442
443     LayoutUnit logicalLeftSide = logicalLeftVisualOverflow();
444     LayoutUnit logicalRightSide = logicalRightVisualOverflow();
445     LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y());
446     LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide;
447     
448     LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
449     LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
450     
451     FloatPoint localPaintOffset(paintOffset);
452     
453     if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
454         return;
455
456     bool isPrinting = renderer().document().printing();
457     
458     // Determine whether or not we're selected.
459     bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
460     if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
461         // When only painting the selection, don't bother to paint if there is none.
462         return;
463
464     if (m_truncation != cNoTruncation) {
465         if (renderer().containingBlock()->style().isLeftToRightDirection() != isLeftToRightDirection()) {
466             // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
467             // at which we start drawing text.
468             // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
469             // |Hello|CBA| -> |...He|CBA|
470             // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
471             // farther to the right.
472             // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
473             // truncated string i.e.  |Hello|CBA| -> |...lo|CBA|
474             LayoutUnit widthOfVisibleText = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
475             LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
476             LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0);
477             localPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
478         }
479     }
480
481     GraphicsContext& context = paintInfo.context();
482
483     const RenderStyle& lineStyle = this->lineStyle();
484     
485     localPaintOffset.move(0, lineStyle.isHorizontalWritingMode() ? 0 : -logicalHeight());
486
487     FloatPoint boxOrigin = locationIncludingFlipping();
488     boxOrigin.moveBy(localPaintOffset);
489     FloatRect boxRect(boxOrigin, FloatSize(logicalWidth(), logicalHeight()));
490
491     auto* combinedText = this->combinedText();
492
493     bool shouldRotate = !isHorizontal() && !combinedText;
494     if (shouldRotate)
495         context.concatCTM(rotation(boxRect, Clockwise));
496
497     // Determine whether or not we have composition underlines to draw.
498     bool containsComposition = renderer().textNode() && renderer().frame().editor().compositionNode() == renderer().textNode();
499     bool useCustomUnderlines = containsComposition && renderer().frame().editor().compositionUsesCustomUnderlines();
500
501     MarkedTextStyle unmarkedStyle = computeStyleForUnmarkedMarkedText(paintInfo);
502
503     // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
504     // and composition underlines.
505     if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
506         if (containsComposition && !useCustomUnderlines)
507             paintCompositionBackground(paintInfo, boxOrigin);
508
509         Vector<MarkedText> markedTexts = collectMarkedTextsForDocumentMarkers(TextPaintPhase::Background);
510 #if ENABLE(TEXT_SELECTION)
511         if (haveSelection && !useCustomUnderlines && !context.paintingDisabled()) {
512             auto selectionMarkedText = createMarkedTextFromSelectionInBox(*this);
513             if (!selectionMarkedText.isEmpty())
514                 markedTexts.append(WTFMove(selectionMarkedText));
515         }
516 #endif
517         auto styledMarkedTexts = subdivideAndResolveStyle(markedTexts, unmarkedStyle, paintInfo);
518
519         // Coalesce styles of adjacent marked texts to minimize the number of drawing commands.
520         auto coalescedStyledMarkedTexts = coalesceAdjacentMarkedTexts(styledMarkedTexts, &MarkedTextStyle::areBackgroundMarkedTextStylesEqual);
521
522         paintMarkedTexts(paintInfo, TextPaintPhase::Background, boxRect, coalescedStyledMarkedTexts);
523     }
524
525     // FIXME: Right now, InlineTextBoxes never call addRelevantUnpaintedObject() even though they might
526     // legitimately be unpainted if they are waiting on a slow-loading web font. We should fix that, and
527     // when we do, we will have to account for the fact the InlineTextBoxes do not always have unique
528     // renderers and Page currently relies on each unpainted object having a unique renderer.
529     if (paintInfo.phase == PaintPhaseForeground)
530         renderer().page().addRelevantRepaintedObject(&renderer(), IntRect(boxOrigin.x(), boxOrigin.y(), logicalWidth(), logicalHeight()));
531
532     // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
533     bool shouldPaintSelectionForeground = haveSelection && !useCustomUnderlines;
534     Vector<MarkedText> markedTexts;
535     if (paintInfo.phase != PaintPhaseSelection) {
536         // The marked texts for the gaps between document markers and selection are implicitly created by subdividing the entire line.
537         markedTexts.append({ clampedOffset(m_start), clampedOffset(end() + 1), MarkedText::Unmarked });
538         if (!isPrinting) {
539             markedTexts.appendVector(collectMarkedTextsForDocumentMarkers(TextPaintPhase::Foreground));
540
541             bool shouldPaintDraggedContent = !(paintInfo.paintBehavior & PaintBehaviorExcludeSelection);
542             if (shouldPaintDraggedContent) {
543                 auto markedTextsForDraggedContent = collectMarkedTextsForDraggedContent();
544                 if (!markedTextsForDraggedContent.isEmpty()) {
545                     shouldPaintSelectionForeground = false;
546                     markedTexts.appendVector(markedTextsForDraggedContent);
547                 }
548             }
549         }
550     }
551     // The selection marked text acts as a placeholder when computing the marked texts for the gaps...
552     if (shouldPaintSelectionForeground) {
553         ASSERT(!isPrinting);
554         auto selectionMarkedText = createMarkedTextFromSelectionInBox(*this);
555         if (!selectionMarkedText.isEmpty())
556             markedTexts.append(WTFMove(selectionMarkedText));
557     }
558
559     auto styledMarkedTexts = subdivideAndResolveStyle(markedTexts, unmarkedStyle, paintInfo);
560
561     // ... now remove the selection marked text if we are excluding selection.
562     if (!isPrinting && paintInfo.paintBehavior & PaintBehaviorExcludeSelection)
563         styledMarkedTexts.removeAllMatching([] (const StyledMarkedText& markedText) { return markedText.type == MarkedText::Selection; });
564
565     // Coalesce styles of adjacent marked texts to minimize the number of drawing commands.
566     auto coalescedStyledMarkedTexts = coalesceAdjacentMarkedTexts(styledMarkedTexts, &MarkedTextStyle::areForegroundMarkedTextStylesEqual);
567
568     paintMarkedTexts(paintInfo, TextPaintPhase::Foreground, boxRect, coalescedStyledMarkedTexts);
569
570     // Paint decorations
571     TextDecoration textDecorations = lineStyle.textDecorationsInEffect();
572     if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) {
573         TextRun textRun = createTextRun();
574         unsigned length = textRun.length();
575         if (m_truncation != cNoTruncation)
576             length = m_truncation;
577         unsigned selectionStart = 0;
578         unsigned selectionEnd = 0;
579         if (haveSelection)
580             std::tie(selectionStart, selectionEnd) = selectionStartEnd();
581
582         FloatRect textDecorationSelectionClipOutRect;
583         if ((paintInfo.paintBehavior & PaintBehaviorExcludeSelection) && selectionStart < selectionEnd && selectionEnd <= length) {
584             textDecorationSelectionClipOutRect = logicalOverflowRect();
585             textDecorationSelectionClipOutRect.moveBy(localPaintOffset);
586             float logicalWidthBeforeRange;
587             float logicalWidthAfterRange;
588             float logicalSelectionWidth = lineFont().widthOfTextRange(textRun, selectionStart, selectionEnd, nullptr, &logicalWidthBeforeRange, &logicalWidthAfterRange);
589             // FIXME: Do we need to handle vertical bottom to top text?
590             if (!isHorizontal()) {
591                 textDecorationSelectionClipOutRect.move(0, logicalWidthBeforeRange);
592                 textDecorationSelectionClipOutRect.setHeight(logicalSelectionWidth);
593             } else if (direction() == RTL) {
594                 textDecorationSelectionClipOutRect.move(logicalWidthAfterRange, 0);
595                 textDecorationSelectionClipOutRect.setWidth(logicalSelectionWidth);
596             } else {
597                 textDecorationSelectionClipOutRect.move(logicalWidthBeforeRange, 0);
598                 textDecorationSelectionClipOutRect.setWidth(logicalSelectionWidth);
599             }
600         }
601
602         // Coalesce styles of adjacent marked texts to minimize the number of drawing commands.
603         auto coalescedStyledMarkedTexts = coalesceAdjacentMarkedTexts(styledMarkedTexts, &MarkedTextStyle::areDecorationMarkedTextStylesEqual);
604
605         paintMarkedTexts(paintInfo, TextPaintPhase::Decoration, boxRect, coalescedStyledMarkedTexts, textDecorationSelectionClipOutRect);
606     }
607
608     // 3. Paint fancy decorations, including composition underlines and platform-specific underlines for spelling errors, grammar errors, et cetera.
609     if (paintInfo.phase == PaintPhaseForeground) {
610         paintPlatformDocumentMarkers(context, boxOrigin);
611
612         if (useCustomUnderlines)
613             paintCompositionUnderlines(paintInfo, boxOrigin);
614     }
615     
616     if (shouldRotate)
617         context.concatCTM(rotation(boxRect, Counterclockwise));
618 }
619
620 unsigned InlineTextBox::clampedOffset(unsigned x) const
621 {
622     unsigned offset = std::max(std::min(x, m_start + m_len), m_start) - m_start;
623     if (m_truncation == cFullTruncation)
624         return offset;
625     if (m_truncation != cNoTruncation)
626         offset = std::min<unsigned>(offset, m_truncation);
627     else if (offset == m_len) {
628         // Fix up the offset if we are combined text or have a hyphen because we manage these embellishments.
629         // That is, they are not reflected in renderer().text(). We treat combined text as a single unit.
630         // We also treat the last codepoint in this box and the hyphen as a single unit.
631         if (auto* combinedText = this->combinedText())
632             offset = combinedText->combinedStringForRendering().length();
633         else if (hasHyphen())
634             offset += lineStyle().hyphenString().length();
635     }
636     return offset;
637 }
638
639 std::pair<unsigned, unsigned> InlineTextBox::selectionStartEnd() const
640 {
641     auto selectionState = renderer().selectionState();
642     if (selectionState == RenderObject::SelectionInside)
643         return { 0, clampedOffset(m_start + m_len) };
644     
645     auto start = renderer().view().selection().startPosition();
646     auto end = renderer().view().selection().endPosition();
647     if (selectionState == RenderObject::SelectionStart)
648         end = renderer().text().length();
649     else if (selectionState == RenderObject::SelectionEnd)
650         start = 0;
651     return { clampedOffset(start), clampedOffset(end) };
652 }
653
654 void InlineTextBox::paintPlatformDocumentMarkers(GraphicsContext& context, const FloatPoint& boxOrigin)
655 {
656     for (auto& markedText : subdivide(collectMarkedTextsForDocumentMarkers(TextPaintPhase::Foreground), OverlapStrategy::Frontmost))
657         paintPlatformDocumentMarker(context, boxOrigin, markedText);
658 }
659
660 void InlineTextBox::paintPlatformDocumentMarker(GraphicsContext& context, const FloatPoint& boxOrigin, const MarkedText& markedText)
661 {
662     // Never print spelling/grammar markers (5327887)
663     if (renderer().document().printing())
664         return;
665
666     if (m_truncation == cFullTruncation)
667         return;
668
669     float start = 0; // start of line to draw, relative to tx
670     float width = m_logicalWidth; // how much line to draw
671
672     // Avoid measuring the text when the entire line box is selected as an optimization.
673     if (markedText.startOffset || markedText.endOffset != clampedOffset(end() + 1)) {
674         // Calculate start & width
675         int deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
676         int selHeight = selectionHeight();
677         FloatPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY);
678         TextRun run = createTextRun();
679
680         LayoutRect selectionRect = LayoutRect(startPoint, FloatSize(0, selHeight));
681         lineFont().adjustSelectionRectForText(run, selectionRect, markedText.startOffset, markedText.endOffset);
682         IntRect markerRect = enclosingIntRect(selectionRect);
683         start = markerRect.x() - startPoint.x();
684         width = markerRect.width();
685     }
686
687     auto lineStyleForMarkedTextType = [] (MarkedText::Type type) {
688         switch (type) {
689         case MarkedText::SpellingError:
690             return GraphicsContext::DocumentMarkerSpellingLineStyle;
691         case MarkedText::GrammarError:
692             return GraphicsContext::DocumentMarkerGrammarLineStyle;
693         case MarkedText::Correction:
694             return GraphicsContext::DocumentMarkerAutocorrectionReplacementLineStyle;
695         case MarkedText::DictationAlternatives:
696             return GraphicsContext::DocumentMarkerDictationAlternativesLineStyle;
697 #if PLATFORM(IOS)
698         case MarkedText::DictationPhraseWithAlternatives:
699             // FIXME: Rename TextCheckingDictationPhraseWithAlternativesLineStyle and remove the PLATFORM(IOS)-guard.
700             return GraphicsContext::TextCheckingDictationPhraseWithAlternativesLineStyle;
701 #endif
702         default:
703             ASSERT_NOT_REACHED();
704             return GraphicsContext::DocumentMarkerSpellingLineStyle;
705         }
706     };
707
708     // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to
709     // make sure to fit within those bounds.  This means the top pixel(s) of the underline will overlap the
710     // bottom pixel(s) of the glyphs in smaller font sizes.  The alternatives are to increase the line spacing (bad!!)
711     // or decrease the underline thickness.  The overlap is actually the most useful, and matches what AppKit does.
712     // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so
713     // we pin to two pixels under the baseline.
714     int lineThickness = cMisspellingLineThickness;
715     int baseline = lineStyle().fontMetrics().ascent();
716     int descent = logicalHeight() - baseline;
717     int underlineOffset;
718     if (descent <= (2 + lineThickness)) {
719         // Place the underline at the very bottom of the text in small/medium fonts.
720         underlineOffset = logicalHeight() - lineThickness;
721     } else {
722         // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
723         underlineOffset = baseline + 2;
724     }
725     context.drawLineForDocumentMarker(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, lineStyleForMarkedTextType(markedText.type));
726 }
727
728 auto InlineTextBox::computeStyleForUnmarkedMarkedText(const PaintInfo& paintInfo) const -> MarkedTextStyle
729 {
730     auto& lineStyle = this->lineStyle();
731
732     MarkedTextStyle style;
733     style.textDecorationStyles = TextDecorationPainter::stylesForRenderer(renderer(), lineStyle.textDecorationsInEffect(), isFirstLine());
734     style.textStyles = computeTextPaintStyle(renderer().frame(), lineStyle, paintInfo);
735     style.textShadow = ShadowData::clone(paintInfo.forceTextColor() ? nullptr : lineStyle.textShadow());
736     style.alpha = 1;
737     return style;
738 }
739
740 auto InlineTextBox::resolveStyleForMarkedText(const MarkedText& markedText, const MarkedTextStyle& baseStyle, const PaintInfo& paintInfo) -> StyledMarkedText
741 {
742     MarkedTextStyle style = baseStyle;
743     switch (markedText.type) {
744     case MarkedText::Correction:
745     case MarkedText::DictationAlternatives:
746 #if PLATFORM(IOS)
747     // FIXME: See <rdar://problem/8933352>. Also, remove the PLATFORM(IOS)-guard.
748     case MarkedText::DictationPhraseWithAlternatives:
749 #endif
750     case MarkedText::GrammarError:
751     case MarkedText::SpellingError:
752     case MarkedText::Unmarked:
753         break;
754     case MarkedText::DraggedContent:
755         style.alpha = 0.25;
756         break;
757     case MarkedText::Selection: {
758         style.textStyles = computeTextSelectionPaintStyle(style.textStyles, renderer(), lineStyle(), paintInfo, style.textShadow);
759
760         Color selectionBackgroundColor = renderer().selectionBackgroundColor();
761         style.backgroundColor = selectionBackgroundColor;
762         if (selectionBackgroundColor.isValid() && selectionBackgroundColor.alpha() && style.textStyles.fillColor == selectionBackgroundColor)
763             style.backgroundColor = { 0xff - selectionBackgroundColor.red(), 0xff - selectionBackgroundColor.green(), 0xff - selectionBackgroundColor.blue() };
764         break;
765     }
766     case MarkedText::TextMatch:
767         style.backgroundColor = markedText.marker->isActiveMatch() ? renderer().theme().platformActiveTextSearchHighlightColor() : renderer().theme().platformInactiveTextSearchHighlightColor();
768         break;
769     }
770     StyledMarkedText styledMarkedText = markedText;
771     styledMarkedText.style = WTFMove(style);
772     return styledMarkedText;
773 }
774
775 auto InlineTextBox::subdivideAndResolveStyle(const Vector<MarkedText>& textsToSubdivide, const MarkedTextStyle& baseStyle, const PaintInfo& paintInfo) -> Vector<StyledMarkedText>
776 {
777     if (textsToSubdivide.isEmpty())
778         return { };
779
780     auto markedTexts = subdivide(textsToSubdivide);
781
782     // Compute frontmost overlapping styled marked texts.
783     Vector<StyledMarkedText> frontmostMarkedTexts;
784     frontmostMarkedTexts.reserveInitialCapacity(markedTexts.size());
785     frontmostMarkedTexts.uncheckedAppend(resolveStyleForMarkedText(markedTexts[0], baseStyle, paintInfo));
786     for (auto it = markedTexts.begin() + 1, end = markedTexts.end(); it != end; ++it) {
787         StyledMarkedText& previousStyledMarkedText = frontmostMarkedTexts.last();
788         if (previousStyledMarkedText.startOffset == it->startOffset && previousStyledMarkedText.endOffset == it->endOffset) {
789             // Marked texts completely cover each other.
790             previousStyledMarkedText = resolveStyleForMarkedText(*it, previousStyledMarkedText.style, paintInfo);
791             continue;
792         }
793         frontmostMarkedTexts.uncheckedAppend(resolveStyleForMarkedText(*it, baseStyle, paintInfo));
794     }
795
796     return frontmostMarkedTexts;
797 }
798
799 auto InlineTextBox::coalesceAdjacentMarkedTexts(const Vector<StyledMarkedText>& textsToCoalesce, MarkedTextStylesEqualityFunction areMarkedTextStylesEqual) -> Vector<StyledMarkedText>
800 {
801     if (textsToCoalesce.isEmpty())
802         return { };
803
804     auto areAdjacentMarkedTextsWithSameStyle = [&] (const StyledMarkedText& a, const StyledMarkedText& b) {
805         return a.endOffset == b.startOffset && areMarkedTextStylesEqual(a.style, b.style);
806     };
807
808     Vector<StyledMarkedText> styledMarkedTexts;
809     styledMarkedTexts.reserveInitialCapacity(textsToCoalesce.size());
810     styledMarkedTexts.uncheckedAppend(textsToCoalesce[0]);
811     for (auto it = textsToCoalesce.begin() + 1, end = textsToCoalesce.end(); it != end; ++it) {
812         StyledMarkedText& previousStyledMarkedText = styledMarkedTexts.last();
813         if (areAdjacentMarkedTextsWithSameStyle(previousStyledMarkedText, *it)) {
814             previousStyledMarkedText.endOffset = it->endOffset;
815             continue;
816         }
817         styledMarkedTexts.uncheckedAppend(*it);
818     }
819
820     return styledMarkedTexts;
821 }
822
823 Vector<MarkedText> InlineTextBox::collectMarkedTextsForDraggedContent()
824 {
825     using DraggendContentRange = std::pair<unsigned, unsigned>;
826     auto draggedContentRanges = renderer().draggedContentRangesBetweenOffsets(m_start, m_start + m_len);
827     Vector<MarkedText> result = draggedContentRanges.map([this] (const DraggendContentRange& range) -> MarkedText {
828         return { clampedOffset(range.first), clampedOffset(range.second), MarkedText::DraggedContent };
829     });
830     return result;
831 }
832
833 Vector<MarkedText> InlineTextBox::collectMarkedTextsForDocumentMarkers(TextPaintPhase phase)
834 {
835     ASSERT(phase == TextPaintPhase::Background || phase == TextPaintPhase::Foreground);
836     if (!renderer().textNode())
837         return { };
838
839     Vector<RenderedDocumentMarker*> markers = renderer().document().markers().markersFor(renderer().textNode());
840
841     auto markedTextTypeForMarkerType = [] (DocumentMarker::MarkerType type) {
842         switch (type) {
843         case DocumentMarker::Spelling:
844             return MarkedText::SpellingError;
845         case DocumentMarker::Grammar:
846             return MarkedText::GrammarError;
847         case DocumentMarker::CorrectionIndicator:
848             return MarkedText::Correction;
849         case DocumentMarker::TextMatch:
850             return MarkedText::TextMatch;
851         case DocumentMarker::DictationAlternatives:
852             return MarkedText::DictationAlternatives;
853 #if PLATFORM(IOS)
854         case DocumentMarker::DictationPhraseWithAlternatives:
855             return MarkedText::DictationPhraseWithAlternatives;
856 #endif
857         default:
858             return MarkedText::Unmarked;
859         }
860     };
861
862     Vector<MarkedText> markedTexts;
863     markedTexts.reserveInitialCapacity(markers.size());
864
865     // Give any document markers that touch this run a chance to draw before the text has been drawn.
866     // Note end() points at the last char, not one past it like endOffset and ranges do.
867     for (auto* marker : markers) {
868         // Collect either the background markers or the foreground markers, but not both
869         switch (marker->type()) {
870         case DocumentMarker::Grammar:
871         case DocumentMarker::Spelling:
872         case DocumentMarker::CorrectionIndicator:
873         case DocumentMarker::Replacement:
874         case DocumentMarker::DictationAlternatives:
875 #if PLATFORM(IOS)
876         // FIXME: Remove the PLATFORM(IOS)-guard.
877         case DocumentMarker::DictationPhraseWithAlternatives:
878 #endif
879             if (phase == TextPaintPhase::Background)
880                 continue;
881             break;
882         case DocumentMarker::TextMatch:
883             if (!renderer().frame().editor().markedTextMatchesAreHighlighted())
884                 continue;
885 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
886             FALLTHROUGH;
887         case DocumentMarker::TelephoneNumber:
888 #endif
889             if (phase == TextPaintPhase::Foreground)
890                 continue;
891             break;
892         default:
893             continue;
894         }
895
896         if (marker->endOffset() <= start()) {
897             // Marker is completely before this run. This might be a marker that sits before the
898             // first run we draw, or markers that were within runs we skipped due to truncation.
899             continue;
900         }
901
902         if (marker->startOffset() > end()) {
903             // Marker is completely after this run, bail. A later run will paint it.
904             break;
905         }
906
907         // Marker intersects this run. Collect it.
908         switch (marker->type()) {
909         case DocumentMarker::Spelling:
910         case DocumentMarker::CorrectionIndicator:
911         case DocumentMarker::DictationAlternatives:
912         case DocumentMarker::Grammar:
913 #if PLATFORM(IOS)
914         // FIXME: See <rdar://problem/8933352>. Also, remove the PLATFORM(IOS)-guard.
915         case DocumentMarker::DictationPhraseWithAlternatives:
916 #endif
917         case DocumentMarker::TextMatch:
918             markedTexts.uncheckedAppend({ clampedOffset(marker->startOffset()), clampedOffset(marker->endOffset()), markedTextTypeForMarkerType(marker->type()), marker });
919             break;
920         case DocumentMarker::Replacement:
921             break;
922 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
923         case DocumentMarker::TelephoneNumber:
924             break;
925 #endif
926         default:
927             ASSERT_NOT_REACHED();
928         }
929     }
930     return markedTexts;
931 }
932
933 FloatPoint InlineTextBox::textOriginFromBoxRect(const FloatRect& boxRect) const
934 {
935     FloatPoint textOrigin { boxRect.x(), boxRect.y() + lineFont().fontMetrics().ascent() };
936     if (auto* combinedText = this->combinedText()) {
937         if (auto newOrigin = combinedText->computeTextOrigin(boxRect))
938             textOrigin = newOrigin.value();
939     }
940     if (isHorizontal())
941         textOrigin.setY(roundToDevicePixel(LayoutUnit { textOrigin.y() }, renderer().document().deviceScaleFactor()));
942     else
943         textOrigin.setX(roundToDevicePixel(LayoutUnit { textOrigin.x() }, renderer().document().deviceScaleFactor()));
944     return textOrigin;
945 }
946
947 void InlineTextBox::paintMarkedTexts(PaintInfo& paintInfo, TextPaintPhase phase, const FloatRect& boxRect, const Vector<StyledMarkedText>& markedTexts, const FloatRect& decorationClipOutRect)
948 {
949     switch (phase) {
950     case TextPaintPhase::Background:
951         for (auto& markedText : markedTexts)
952             paintMarkedTextBackground(paintInfo, boxRect.location(), markedText.style.backgroundColor, markedText.startOffset, markedText.endOffset);
953         return;
954     case TextPaintPhase::Foreground:
955         for (auto& markedText : markedTexts)
956             paintMarkedTextForeground(paintInfo, boxRect, markedText);
957         return;
958     case TextPaintPhase::Decoration:
959         for (auto& markedText : markedTexts)
960             paintMarkedTextDecoration(paintInfo, boxRect, decorationClipOutRect, markedText);
961         return;
962     }
963 }
964
965 void InlineTextBox::paintMarkedTextBackground(PaintInfo& paintInfo, const FloatPoint& boxOrigin, const Color& color, unsigned clampedStartOffset, unsigned clampedEndOffset)
966 {
967     if (clampedStartOffset >= clampedEndOffset)
968         return;
969
970     GraphicsContext& context = paintInfo.context();
971     GraphicsContextStateSaver stateSaver { context };
972     updateGraphicsContext(context, TextPaintStyle { color }); // Don't draw text at all!
973
974     // Note that if the text is truncated, we let the thing being painted in the truncation
975     // draw its own highlight.
976     TextRun textRun = createTextRun();
977
978     const RootInlineBox& rootBox = root();
979     LayoutUnit selectionBottom = rootBox.selectionBottom();
980     LayoutUnit selectionTop = rootBox.selectionTopAdjustedForPrecedingBlock();
981
982     // Use same y positioning and height as for selection, so that when the selection and this subrange are on
983     // the same word there are no pieces sticking out.
984     LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop;
985     LayoutUnit selectionHeight = std::max<LayoutUnit>(0, selectionBottom - selectionTop);
986
987     LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selectionHeight);
988     lineFont().adjustSelectionRectForText(textRun, selectionRect, clampedStartOffset, clampedEndOffset);
989
990     // FIXME: Support painting combined text. See <https://bugs.webkit.org/show_bug.cgi?id=180993>.
991     context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), color);
992 }
993
994 void InlineTextBox::paintMarkedTextForeground(PaintInfo& paintInfo, const FloatRect& boxRect, const StyledMarkedText& markedText)
995 {
996     if (markedText.startOffset >= markedText.endOffset)
997         return;
998
999     GraphicsContext& context = paintInfo.context();
1000     const FontCascade& font = lineFont();
1001     const RenderStyle& lineStyle = this->lineStyle();
1002
1003     float emphasisMarkOffset = 0;
1004     bool emphasisMarkAbove;
1005     bool hasTextEmphasis = emphasisMarkExistsAndIsAbove(lineStyle, emphasisMarkAbove);
1006     const AtomicString& emphasisMark = hasTextEmphasis ? lineStyle.textEmphasisMarkString() : nullAtom();
1007     if (!emphasisMark.isEmpty())
1008         emphasisMarkOffset = emphasisMarkAbove ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
1009
1010     TextPainter textPainter { context };
1011     textPainter.setFont(font);
1012     textPainter.setStyle(markedText.style.textStyles);
1013     textPainter.setIsHorizontal(isHorizontal());
1014     if (markedText.style.textShadow)
1015         textPainter.setShadow(&markedText.style.textShadow.value());
1016     textPainter.setEmphasisMark(emphasisMark, emphasisMarkOffset, combinedText());
1017
1018     TextRun textRun = createTextRun();
1019     textPainter.setGlyphDisplayListIfNeeded(*this, paintInfo, font, context, textRun);
1020
1021     GraphicsContextStateSaver stateSaver { context, false };
1022     if (markedText.type == MarkedText::DraggedContent) {
1023         stateSaver.save();
1024         context.setAlpha(markedText.style.alpha);
1025     }
1026     // TextPainter wants the box rectangle and text origin of the entire line box.
1027     textPainter.paintRange(textRun, boxRect, textOriginFromBoxRect(boxRect), markedText.startOffset, markedText.endOffset);
1028 }
1029
1030 void InlineTextBox::paintMarkedTextDecoration(PaintInfo& paintInfo, const FloatRect& boxRect, const FloatRect& clipOutRect, const StyledMarkedText& markedText)
1031 {
1032     if (m_truncation == cFullTruncation)
1033         return;
1034
1035     GraphicsContext& context = paintInfo.context();
1036     updateGraphicsContext(context, markedText.style.textStyles);
1037
1038     bool isCombinedText = combinedText();
1039     if (isCombinedText)
1040         context.concatCTM(rotation(boxRect, Clockwise));
1041
1042     // 1. Compute text selection
1043     unsigned startOffset = markedText.startOffset;
1044     unsigned endOffset = markedText.endOffset;
1045     if (startOffset >= endOffset)
1046         return;
1047
1048     // Note that if the text is truncated, we let the thing being painted in the truncation
1049     // draw its own decoration.
1050     TextRun textRun = createTextRun();
1051
1052     // Avoid measuring the text when the entire line box is selected as an optimization.
1053     FloatRect snappedSelectionRect = boxRect;
1054     if (startOffset || endOffset != textRun.length()) {
1055         LayoutRect selectionRect = { boxRect.x(), boxRect.y(), boxRect.width(), boxRect.height() };
1056         lineFont().adjustSelectionRectForText(textRun, selectionRect, startOffset, endOffset);
1057         snappedSelectionRect = snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr());
1058     }
1059
1060     // 2. Paint
1061     TextDecorationPainter decorationPainter { context, static_cast<unsigned>(lineStyle().textDecorationsInEffect()), renderer(), isFirstLine(), markedText.style.textDecorationStyles };
1062     decorationPainter.setInlineTextBox(this);
1063     decorationPainter.setFont(lineFont());
1064     decorationPainter.setWidth(snappedSelectionRect.width());
1065     decorationPainter.setBaseline(lineStyle().fontMetrics().ascent());
1066     decorationPainter.setIsHorizontal(isHorizontal());
1067     if (markedText.style.textShadow)
1068         decorationPainter.addTextShadow(&markedText.style.textShadow.value());
1069
1070     {
1071         GraphicsContextStateSaver stateSaver { context, false };
1072         if (!clipOutRect.isEmpty()) {
1073             stateSaver.save();
1074             context.clipOut(clipOutRect);
1075         }
1076         decorationPainter.paintTextDecoration(textRun.subRun(startOffset, endOffset - startOffset), textOriginFromBoxRect(snappedSelectionRect), snappedSelectionRect.location());
1077     }
1078
1079     if (isCombinedText)
1080         context.concatCTM(rotation(boxRect, Counterclockwise));
1081 }
1082
1083 void InlineTextBox::paintCompositionBackground(PaintInfo& paintInfo, const FloatPoint& boxOrigin)
1084 {
1085     paintMarkedTextBackground(paintInfo, boxOrigin, Color::compositionFill, clampedOffset(renderer().frame().editor().compositionStart()), clampedOffset(renderer().frame().editor().compositionEnd()));
1086 }
1087
1088 void InlineTextBox::paintCompositionUnderlines(PaintInfo& paintInfo, const FloatPoint& boxOrigin) const
1089 {
1090     if (m_truncation == cFullTruncation)
1091         return;
1092
1093     for (auto& underline : renderer().frame().editor().customCompositionUnderlines()) {
1094         if (underline.endOffset <= m_start) {
1095             // Underline is completely before this run. This might be an underline that sits
1096             // before the first run we draw, or underlines that were within runs we skipped
1097             // due to truncation.
1098             continue;
1099         }
1100
1101         if (underline.startOffset > end())
1102             break; // Underline is completely after this run, bail. A later run will paint it.
1103
1104         // Underline intersects this run. Paint it.
1105         paintCompositionUnderline(paintInfo, boxOrigin, underline);
1106
1107         if (underline.endOffset > end() + 1)
1108             break; // Underline also runs into the next run. Bail now, no more marker advancement.
1109     }
1110 }
1111
1112 static inline void mirrorRTLSegment(float logicalWidth, TextDirection direction, float& start, float width)
1113 {
1114     if (direction == LTR)
1115         return;
1116     start = logicalWidth - width - start;
1117 }
1118
1119 void InlineTextBox::paintCompositionUnderline(PaintInfo& paintInfo, const FloatPoint& boxOrigin, const CompositionUnderline& underline) const
1120 {
1121     if (m_truncation == cFullTruncation)
1122         return;
1123     
1124     float start = 0; // start of line to draw, relative to tx
1125     float width = m_logicalWidth; // how much line to draw
1126     bool useWholeWidth = true;
1127     unsigned paintStart = m_start;
1128     unsigned paintEnd = end() + 1; // end points at the last char, not past it
1129     if (paintStart <= underline.startOffset) {
1130         paintStart = underline.startOffset;
1131         useWholeWidth = false;
1132         start = renderer().width(m_start, paintStart - m_start, textPos(), isFirstLine());
1133     }
1134     if (paintEnd != underline.endOffset) {      // end points at the last char, not past it
1135         paintEnd = std::min(paintEnd, (unsigned)underline.endOffset);
1136         useWholeWidth = false;
1137     }
1138     if (m_truncation != cNoTruncation) {
1139         paintEnd = std::min(paintEnd, (unsigned)m_start + m_truncation);
1140         useWholeWidth = false;
1141     }
1142     if (!useWholeWidth) {
1143         width = renderer().width(paintStart, paintEnd - paintStart, textPos() + start, isFirstLine());
1144         mirrorRTLSegment(m_logicalWidth, direction(), start, width);
1145     }
1146
1147     // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
1148     // All other marked text underlines are 1px thick.
1149     // If there's not enough space the underline will touch or overlap characters.
1150     int lineThickness = 1;
1151     int baseline = lineStyle().fontMetrics().ascent();
1152     if (underline.thick && logicalHeight() - baseline >= 2)
1153         lineThickness = 2;
1154
1155     // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
1156     // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
1157     start += 1;
1158     width -= 2;
1159
1160     GraphicsContext& context = paintInfo.context();
1161     context.setStrokeColor(underline.compositionUnderlineColor == CompositionUnderlineColor::TextColor ? renderer().style().visitedDependentColor(CSSPropertyWebkitTextFillColor) : underline.color);
1162     context.setStrokeThickness(lineThickness);
1163     context.drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, renderer().document().printing());
1164 }
1165
1166 int InlineTextBox::caretMinOffset() const
1167 {
1168     return m_start;
1169 }
1170
1171 int InlineTextBox::caretMaxOffset() const
1172 {
1173     return m_start + m_len;
1174 }
1175
1176 float InlineTextBox::textPos() const
1177 {
1178     // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset
1179     // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width.
1180     if (logicalLeft() == 0)
1181         return 0;
1182     return logicalLeft() - root().logicalLeft();
1183 }
1184
1185 int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs) const
1186 {
1187     if (isLineBreak())
1188         return 0;
1189     if (lineOffset - logicalLeft() > logicalWidth())
1190         return isLeftToRightDirection() ? len() : 0;
1191     if (lineOffset - logicalLeft() < 0)
1192         return isLeftToRightDirection() ? 0 : len();
1193     bool ignoreCombinedText = true;
1194     bool ignoreHyphen = true;
1195     return lineFont().offsetForPosition(createTextRun(ignoreCombinedText, ignoreHyphen), lineOffset - logicalLeft(), includePartialGlyphs);
1196 }
1197
1198 float InlineTextBox::positionForOffset(unsigned offset) const
1199 {
1200     ASSERT(offset >= m_start);
1201     ASSERT(offset <= m_start + len());
1202
1203     if (isLineBreak())
1204         return logicalLeft();
1205
1206     unsigned startOffset;
1207     unsigned endOffset;
1208     if (isLeftToRightDirection()) {
1209         startOffset = 0;
1210         endOffset = clampedOffset(offset);
1211     } else {
1212         startOffset = clampedOffset(offset);
1213         endOffset = m_len;
1214     }
1215
1216     // FIXME: Do we need to add rightBearing here?
1217     LayoutRect selectionRect = LayoutRect(logicalLeft(), 0, 0, 0);
1218     bool ignoreCombinedText = true;
1219     bool ignoreHyphen = true;
1220     TextRun textRun = createTextRun(ignoreCombinedText, ignoreHyphen);
1221     lineFont().adjustSelectionRectForText(textRun, selectionRect, startOffset, endOffset);
1222     return snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()).maxX();
1223 }
1224
1225 TextRun InlineTextBox::createTextRun(bool ignoreCombinedText, bool ignoreHyphen) const
1226 {
1227     const auto& style = lineStyle();
1228     TextRun textRun { text(ignoreCombinedText, ignoreHyphen), textPos(), expansion(), expansionBehavior(), direction(), dirOverride() || style.rtlOrdering() == VisualOrder, !renderer().canUseSimpleFontCodePath() };
1229     textRun.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
1230     return textRun;
1231 }
1232
1233 String InlineTextBox::text(bool ignoreCombinedText, bool ignoreHyphen) const
1234 {
1235     if (auto* combinedText = this->combinedText()) {
1236         if (ignoreCombinedText)
1237             return renderer().text().substring(m_start, m_len);
1238         return combinedText->combinedStringForRendering();
1239     }
1240     if (hasHyphen()) {
1241         if (ignoreHyphen)
1242             return renderer().text().substring(m_start, m_len);
1243         return makeString(StringView(renderer().text()).substring(m_start, m_len), lineStyle().hyphenString());
1244     }
1245     return renderer().text().substring(m_start, m_len);
1246 }
1247
1248 inline const RenderCombineText* InlineTextBox::combinedText() const
1249 {
1250     return lineStyle().hasTextCombine() && is<RenderCombineText>(renderer()) && downcast<RenderCombineText>(renderer()).isCombined() ? &downcast<RenderCombineText>(renderer()) : nullptr;
1251 }
1252
1253 ExpansionBehavior InlineTextBox::expansionBehavior() const
1254 {
1255     ExpansionBehavior leadingBehavior;
1256     if (forceLeadingExpansion())
1257         leadingBehavior = ForceLeadingExpansion;
1258     else if (canHaveLeadingExpansion())
1259         leadingBehavior = AllowLeadingExpansion;
1260     else
1261         leadingBehavior = ForbidLeadingExpansion;
1262
1263     ExpansionBehavior trailingBehavior;
1264     if (forceTrailingExpansion())
1265         trailingBehavior = ForceTrailingExpansion;
1266     else if (expansion() && nextLeafChild() && !nextLeafChild()->isLineBreak())
1267         trailingBehavior = AllowTrailingExpansion;
1268     else
1269         trailingBehavior = ForbidTrailingExpansion;
1270
1271     return leadingBehavior | trailingBehavior;
1272 }
1273
1274 #if ENABLE(TREE_DEBUGGING)
1275
1276 const char* InlineTextBox::boxName() const
1277 {
1278     return "InlineTextBox";
1279 }
1280
1281 void InlineTextBox::outputLineBox(TextStream& stream, bool mark, int depth) const
1282 {
1283     stream << "-------- " << (isDirty() ? "D" : "-") << "-";
1284
1285     int printedCharacters = 0;
1286     if (mark) {
1287         stream << "*";
1288         ++printedCharacters;
1289     }
1290     while (++printedCharacters <= depth * 2)
1291         stream << " ";
1292
1293     String value = renderer().text();
1294     value = value.substring(start(), len());
1295     value.replaceWithLiteral('\\', "\\\\");
1296     value.replaceWithLiteral('\n', "\\n");
1297     stream << boxName() << " " << FloatRect(x(), y(), width(), height()) << " (" << this << ") renderer->(" << &renderer() << ") run(" << start() << ", " << start() + len() << ") \"" << value.utf8().data() << "\"";
1298     stream.nextLine();
1299 }
1300
1301 #endif
1302
1303 } // namespace WebCore