Share logic in InlineTextBox to compute selection rect
[WebKit.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 "MarkerSubrange.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/text/CString.h>
54 #include <wtf/text/TextStream.h>
55
56 namespace WebCore {
57
58 struct SameSizeAsInlineTextBox : public InlineBox {
59     unsigned variables[1];
60     unsigned short variables2[2];
61     void* pointers[2];
62 };
63
64 COMPILE_ASSERT(sizeof(InlineTextBox) == sizeof(SameSizeAsInlineTextBox), InlineTextBox_should_stay_small);
65
66 typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap;
67 static InlineTextBoxOverflowMap* gTextBoxesWithOverflow;
68
69 InlineTextBox::~InlineTextBox()
70 {
71     if (!knownToHaveNoOverflow() && gTextBoxesWithOverflow)
72         gTextBoxesWithOverflow->remove(this);
73 }
74
75 void InlineTextBox::markDirty(bool dirty)
76 {
77     if (dirty) {
78         m_len = 0;
79         m_start = 0;
80     }
81     InlineBox::markDirty(dirty);
82 }
83
84 LayoutRect InlineTextBox::logicalOverflowRect() const
85 {
86     if (knownToHaveNoOverflow() || !gTextBoxesWithOverflow)
87         return enclosingIntRect(logicalFrameRect());
88     return gTextBoxesWithOverflow->get(this);
89 }
90
91 void InlineTextBox::setLogicalOverflowRect(const LayoutRect& rect)
92 {
93     ASSERT(!knownToHaveNoOverflow());
94     if (!gTextBoxesWithOverflow)
95         gTextBoxesWithOverflow = new InlineTextBoxOverflowMap;
96     gTextBoxesWithOverflow->add(this, rect);
97 }
98
99 int InlineTextBox::baselinePosition(FontBaseline baselineType) const
100 {
101     if (!parent())
102         return 0;
103     if (&parent()->renderer() == renderer().parent())
104         return parent()->baselinePosition(baselineType);
105     return downcast<RenderBoxModelObject>(*renderer().parent()).baselinePosition(baselineType, isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
106 }
107
108 LayoutUnit InlineTextBox::lineHeight() const
109 {
110     if (!renderer().parent())
111         return 0;
112     if (&parent()->renderer() == renderer().parent())
113         return parent()->lineHeight();
114     return downcast<RenderBoxModelObject>(*renderer().parent()).lineHeight(isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
115 }
116
117 LayoutUnit InlineTextBox::selectionTop() const
118 {
119     return root().selectionTop();
120 }
121
122 LayoutUnit InlineTextBox::selectionBottom() const
123 {
124     return root().selectionBottom();
125 }
126
127 LayoutUnit InlineTextBox::selectionHeight() const
128 {
129     return root().selectionHeight();
130 }
131
132 bool InlineTextBox::isSelected(unsigned startPosition, unsigned endPosition) const
133 {
134     return clampedOffset(startPosition) < clampedOffset(endPosition);
135 }
136
137 RenderObject::SelectionState InlineTextBox::selectionState()
138 {
139     RenderObject::SelectionState state = renderer().selectionState();
140     if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
141         auto& selection = renderer().view().selection();
142         auto startPos = selection.startPosition();
143         auto endPos = selection.endPosition();
144         // The position after a hard line break is considered to be past its end.
145         ASSERT(start() + len() >= (isLineBreak() ? 1 : 0));
146         unsigned lastSelectable = start() + len() - (isLineBreak() ? 1 : 0);
147
148         bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
149         bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
150         if (start && end)
151             state = RenderObject::SelectionBoth;
152         else if (start)
153             state = RenderObject::SelectionStart;
154         else if (end)
155             state = RenderObject::SelectionEnd;
156         else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
157                  (state == RenderObject::SelectionStart || endPos > lastSelectable))
158             state = RenderObject::SelectionInside;
159         else if (state == RenderObject::SelectionBoth)
160             state = RenderObject::SelectionNone;
161     }
162
163     // If there are ellipsis following, make sure their selection is updated.
164     if (m_truncation != cNoTruncation && root().ellipsisBox()) {
165         EllipsisBox* ellipsis = root().ellipsisBox();
166         if (state != RenderObject::SelectionNone) {
167             unsigned selectionStart;
168             unsigned selectionEnd;
169             std::tie(selectionStart, selectionEnd) = selectionStartEnd();
170             // The ellipsis should be considered to be selected if the end of
171             // the selection is past the beginning of the truncation and the
172             // beginning of the selection is before or at the beginning of the
173             // truncation.
174             ellipsis->setSelectionState(selectionEnd >= m_truncation && selectionStart <= m_truncation ?
175                 RenderObject::SelectionInside : RenderObject::SelectionNone);
176         } else
177             ellipsis->setSelectionState(RenderObject::SelectionNone);
178     }
179
180     return state;
181 }
182
183 inline const FontCascade& InlineTextBox::lineFont() const
184 {
185     return combinedText() ? combinedText()->textCombineFont() : lineStyle().fontCascade();
186 }
187
188 // FIXME: This function should return the same rectangle as localSelectionRectWithClampedPositions(..., ..., SelectionRectRounding::None),
189 // which represents the rectange of the selected text on the page. We need to update all callers to expect this change.
190 // See <https://bugs.webkit.org/show_bug.cgi?id=138913> for more details.
191 LayoutRect InlineTextBox::localSelectionRect(unsigned startPosition, unsigned endPosition) const
192 {
193     if (startPosition > endPosition || endPosition < m_start || startPosition > end() + 1)
194         return { };
195     return localSelectionRectWithClampedPositions(clampedOffset(startPosition), clampedOffset(endPosition), SelectionRectRounding::EnclosingIntRect);
196 }
197
198 LayoutRect InlineTextBox::localSelectionRectWithClampedPositions(unsigned clampedStartPosition, unsigned clampedEndPosition, SelectionRectRounding roundingStrategy) const
199 {
200     if (clampedStartPosition > clampedEndPosition)
201         return { };
202
203     auto selectionTop = root().selectionTopAdjustedForPrecedingBlock();
204     auto selectionBottom = this->selectionBottom();
205
206     auto text = this->text();
207     TextRun textRun = createTextRun(text);
208
209     // Use same y positioning and height as used for selected text on the page, so that when some or all of this
210     // subrange is selected on the page there are no pieces sticking out.
211     LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop;
212     auto selectionHeight = root().selectionHeightAdjustedForPrecedingBlock();
213     LayoutRect selectionRect { LayoutPoint { logicalLeft(), logicalTop() - deltaY }, LayoutSize { m_logicalWidth, selectionHeight } };
214
215     // Avoid computing the font width when the entire line box is selected as an optimization.
216     if (clampedStartPosition || clampedEndPosition != textRun.length())
217         lineFont().adjustSelectionRectForText(textRun, selectionRect, clampedStartPosition, clampedEndPosition);
218
219     LayoutUnit selectionLeft;
220     LayoutUnit selectionWidth;
221     if (roundingStrategy == SelectionRectRounding::EnclosingIntRect) {
222         auto snappedSelectionRect = enclosingIntRect(selectionRect);
223         selectionLeft = snappedSelectionRect.x();
224         selectionWidth = snappedSelectionRect.width();
225         if (snappedSelectionRect.x() > logicalRight())
226             selectionWidth = 0;
227         else if (snappedSelectionRect.maxX() > logicalRight())
228             selectionWidth = logicalRight() - snappedSelectionRect.x();
229     } else {
230         selectionLeft = selectionRect.x();
231         selectionWidth = selectionRect.width();
232     }
233
234     LayoutPoint location;
235     LayoutSize size;
236     if (isHorizontal()) {
237         location = { selectionLeft, selectionTop };
238         size = { selectionWidth, selectionHeight };
239     } else {
240         location = { selectionTop, selectionLeft };
241         size = { selectionHeight, selectionWidth };
242     }
243
244     return { location, size };
245 }
246
247 void InlineTextBox::deleteLine()
248 {
249     renderer().removeTextBox(*this);
250     delete this;
251 }
252
253 void InlineTextBox::extractLine()
254 {
255     if (extracted())
256         return;
257
258     renderer().extractTextBox(*this);
259 }
260
261 void InlineTextBox::attachLine()
262 {
263     if (!extracted())
264         return;
265     
266     renderer().attachTextBox(*this);
267 }
268
269 float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, float &truncatedWidth, bool& foundBox)
270 {
271     if (foundBox) {
272         m_truncation = cFullTruncation;
273         return -1;
274     }
275
276     // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
277     float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
278     
279     // Criteria for full truncation:
280     // LTR: the left edge of the ellipsis is to the left of our text run.
281     // RTL: the right edge of the ellipsis is to the right of our text run.
282     bool ltrFullTruncation = flowIsLTR && ellipsisX <= left();
283     bool rtlFullTruncation = !flowIsLTR && ellipsisX >= left() + logicalWidth();
284     if (ltrFullTruncation || rtlFullTruncation) {
285         // Too far.  Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
286         m_truncation = cFullTruncation;
287         foundBox = true;
288         return -1;
289     }
290
291     bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < right());
292     bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > left());
293     if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
294         foundBox = true;
295
296         // The inline box may have different directionality than it's parent.  Since truncation
297         // behavior depends both on both the parent and the inline block's directionality, we
298         // must keep track of these separately.
299         bool ltr = isLeftToRightDirection();
300         if (ltr != flowIsLTR) {
301           // Width in pixels of the visible portion of the box, excluding the ellipsis.
302           int visibleBoxWidth = visibleRightEdge - visibleLeftEdge  - ellipsisWidth;
303           ellipsisX = ltr ? left() + visibleBoxWidth : right() - visibleBoxWidth;
304         }
305
306         int offset = offsetForPosition(ellipsisX, false);
307         if (offset == 0) {
308             // No characters should be rendered.  Set ourselves to full truncation and place the ellipsis at the min of our start
309             // and the ellipsis edge.
310             m_truncation = cFullTruncation;
311             truncatedWidth += ellipsisWidth;
312             return flowIsLTR ? std::min(ellipsisX, x()) : std::max(ellipsisX, right() - ellipsisWidth);
313         }
314
315         // Set the truncation index on the text run.
316         m_truncation = offset;
317
318         // If we got here that means that we were only partially truncated and we need to return the pixel offset at which
319         // to place the ellipsis.
320         float widthOfVisibleText = renderer().width(m_start, offset, textPos(), isFirstLine());
321
322         // The ellipsis needs to be placed just after the last visible character.
323         // Where "after" is defined by the flow directionality, not the inline
324         // box directionality.
325         // e.g. In the case of an LTR inline box truncated in an RTL flow then we can
326         // have a situation such as |Hello| -> |...He|
327         truncatedWidth += widthOfVisibleText + ellipsisWidth;
328         if (flowIsLTR)
329             return left() + widthOfVisibleText;
330         else
331             return right() - widthOfVisibleText - ellipsisWidth;
332     }
333     truncatedWidth += logicalWidth();
334     return -1;
335 }
336
337
338
339 bool InlineTextBox::isLineBreak() const
340 {
341     return renderer().style().preserveNewline() && len() == 1 && (*renderer().text())[start()] == '\n';
342 }
343
344 bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/,
345     HitTestAction /*hitTestAction*/)
346 {
347     if (!visibleToHitTesting())
348         return false;
349
350     if (isLineBreak())
351         return false;
352
353     if (m_truncation == cFullTruncation)
354         return false;
355
356     FloatRect rect(locationIncludingFlipping(), size());
357     // Make sure truncated text is ignored while hittesting.
358     if (m_truncation != cNoTruncation) {
359         LayoutUnit widthOfVisibleText = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
360
361         if (isHorizontal())
362             renderer().style().isLeftToRightDirection() ? rect.setWidth(widthOfVisibleText) : rect.shiftXEdgeTo(right() - widthOfVisibleText);
363         else
364             rect.setHeight(widthOfVisibleText);
365     }
366
367     rect.moveBy(accumulatedOffset);
368
369     if (locationInContainer.intersects(rect)) {
370         renderer().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset)));
371         if (result.addNodeToListBasedTestResult(renderer().textNode(), request, locationInContainer, rect) == HitTestProgress::Stop)
372             return true;
373     }
374     return false;
375 }
376
377 static inline bool emphasisPositionHasNeitherLeftNorRight(TextEmphasisPosition emphasisPosition)
378 {
379     return !(emphasisPosition & TextEmphasisPositionLeft) && !(emphasisPosition & TextEmphasisPositionRight);
380 }
381
382 bool InlineTextBox::emphasisMarkExistsAndIsAbove(const RenderStyle& style, bool& above) const
383 {
384     // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
385     if (style.textEmphasisMark() == TextEmphasisMarkNone)
386         return false;
387
388     TextEmphasisPosition emphasisPosition = style.textEmphasisPosition();
389     ASSERT(!((emphasisPosition & TextEmphasisPositionOver) && (emphasisPosition & TextEmphasisPositionUnder)));
390     ASSERT(!((emphasisPosition & TextEmphasisPositionLeft) && (emphasisPosition & TextEmphasisPositionRight)));
391     
392     if (emphasisPositionHasNeitherLeftNorRight(emphasisPosition))
393         above = emphasisPosition & TextEmphasisPositionOver;
394     else if (style.isHorizontalWritingMode())
395         above = emphasisPosition & TextEmphasisPositionOver;
396     else
397         above = emphasisPosition & TextEmphasisPositionRight;
398     
399     if ((style.isHorizontalWritingMode() && (emphasisPosition & TextEmphasisPositionUnder))
400         || (!style.isHorizontalWritingMode() && (emphasisPosition & TextEmphasisPositionLeft)))
401         return true; // Ruby text is always over, so it cannot suppress emphasis marks under.
402
403     RenderBlock* containingBlock = renderer().containingBlock();
404     if (!containingBlock->isRubyBase())
405         return true; // This text is not inside a ruby base, so it does not have ruby text over it.
406
407     if (!is<RenderRubyRun>(*containingBlock->parent()))
408         return true; // Cannot get the ruby text.
409
410     RenderRubyText* rubyText = downcast<RenderRubyRun>(*containingBlock->parent()).rubyText();
411
412     // The emphasis marks over are suppressed only if there is a ruby text box and it not empty.
413     return !rubyText || !rubyText->hasLines();
414 }
415
416 void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/)
417 {
418     if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer().style().visibility() != VISIBLE
419         || m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len)
420         return;
421
422     ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
423
424     LayoutUnit logicalLeftSide = logicalLeftVisualOverflow();
425     LayoutUnit logicalRightSide = logicalRightVisualOverflow();
426     LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y());
427     LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide;
428     
429     LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
430     LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
431     
432     LayoutPoint localPaintOffset { paintOffset };
433     
434     if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
435         return;
436
437     bool isPrinting = renderer().document().printing();
438     
439     // Determine whether or not we're selected.
440     bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
441     if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
442         // When only painting the selection, don't bother to paint if there is none.
443         return;
444
445     if (m_truncation != cNoTruncation) {
446         if (renderer().containingBlock()->style().isLeftToRightDirection() != isLeftToRightDirection()) {
447             // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
448             // at which we start drawing text.
449             // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
450             // |Hello|CBA| -> |...He|CBA|
451             // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
452             // farther to the right.
453             // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
454             // truncated string i.e.  |Hello|CBA| -> |...lo|CBA|
455             LayoutUnit widthOfVisibleText = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
456             LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
457             LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0);
458             localPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
459         }
460     }
461
462     GraphicsContext& context = paintInfo.context();
463
464     const RenderStyle& lineStyle = this->lineStyle();
465     
466     localPaintOffset.move(0, lineStyle.isHorizontalWritingMode() ? 0 : -logicalHeight());
467
468     FloatPoint boxOrigin = locationIncludingFlipping();
469     boxOrigin.moveBy(localPaintOffset);
470     FloatRect boxRect(boxOrigin, FloatSize(logicalWidth(), logicalHeight()));
471
472     auto* combinedText = this->combinedText();
473
474     bool shouldRotate = !isHorizontal() && !combinedText;
475     if (shouldRotate)
476         context.concatCTM(rotation(boxRect, Clockwise));
477
478     // Determine whether or not we have composition underlines to draw.
479     bool containsComposition = renderer().textNode() && renderer().frame().editor().compositionNode() == renderer().textNode();
480     bool useCustomUnderlines = containsComposition && renderer().frame().editor().compositionUsesCustomUnderlines();
481
482     // Determine the text colors and selection colors.
483     TextPaintStyle textPaintStyle = computeTextPaintStyle(renderer().frame(), lineStyle, paintInfo);
484
485     bool paintSelectedTextOnly = false;
486     bool paintSelectedTextSeparately = false;
487     bool paintNonSelectedTextOnly = false;
488     const ShadowData* selectionShadow = nullptr;
489     
490     // Text with custom underlines does not have selection background painted, so selection paint style is not appropriate for it.
491     TextPaintStyle selectionPaintStyle = haveSelection && !useCustomUnderlines ? computeTextSelectionPaintStyle(textPaintStyle, renderer(), lineStyle, paintInfo, paintSelectedTextOnly, paintSelectedTextSeparately, paintNonSelectedTextOnly, selectionShadow) : textPaintStyle;
492
493     // Set our font.
494     const FontCascade& font = lineFont();
495     // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
496     // and composition underlines.
497     if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
498         if (containsComposition && !useCustomUnderlines)
499             paintCompositionBackground(context, localPaintOffset);
500
501         paintDocumentMarkers(context, localPaintOffset, true);
502
503         if (haveSelection && !useCustomUnderlines)
504             paintSelection(context, localPaintOffset, selectionPaintStyle.fillColor);
505     }
506
507     // FIXME: Right now, InlineTextBoxes never call addRelevantUnpaintedObject() even though they might
508     // legitimately be unpainted if they are waiting on a slow-loading web font. We should fix that, and
509     // when we do, we will have to account for the fact the InlineTextBoxes do not always have unique
510     // renderers and Page currently relies on each unpainted object having a unique renderer.
511     if (paintInfo.phase == PaintPhaseForeground)
512         renderer().page().addRelevantRepaintedObject(&renderer(), IntRect(boxOrigin.x(), boxOrigin.y(), logicalWidth(), logicalHeight()));
513
514     // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
515     auto text = this->text();
516     TextRun textRun = createTextRun(text);
517     unsigned length = textRun.length();
518     if (m_truncation != cNoTruncation)
519         length = m_truncation;
520
521     unsigned selectionStart = 0;
522     unsigned selectionEnd = 0;
523     if (haveSelection && (paintSelectedTextOnly || paintSelectedTextSeparately))
524         std::tie(selectionStart, selectionEnd) = selectionStartEnd();
525
526     float emphasisMarkOffset = 0;
527     bool emphasisMarkAbove;
528     bool hasTextEmphasis = emphasisMarkExistsAndIsAbove(lineStyle, emphasisMarkAbove);
529     const AtomicString& emphasisMark = hasTextEmphasis ? lineStyle.textEmphasisMarkString() : nullAtom();
530     if (!emphasisMark.isEmpty())
531         emphasisMarkOffset = emphasisMarkAbove ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
532
533     const ShadowData* textShadow = (paintInfo.forceTextColor()) ? nullptr : lineStyle.textShadow();
534
535     FloatPoint textOrigin(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());
536     if (combinedText) {
537         if (auto newOrigin = combinedText->computeTextOrigin(boxRect))
538             textOrigin = newOrigin.value();
539     }
540
541     if (isHorizontal())
542         textOrigin.setY(roundToDevicePixel(LayoutUnit(textOrigin.y()), renderer().document().deviceScaleFactor()));
543     else
544         textOrigin.setX(roundToDevicePixel(LayoutUnit(textOrigin.x()), renderer().document().deviceScaleFactor()));
545
546     TextPainter textPainter(context);
547     textPainter.setFont(font);
548     textPainter.setStyle(textPaintStyle);
549     textPainter.setSelectionStyle(selectionPaintStyle);
550     textPainter.setIsHorizontal(isHorizontal());
551     textPainter.setShadow(textShadow);
552     textPainter.setSelectionShadow(selectionShadow);
553     textPainter.setEmphasisMark(emphasisMark, emphasisMarkOffset, combinedText);
554
555     auto draggedContentRanges = renderer().draggedContentRangesBetweenOffsets(m_start, m_start + m_len);
556     if (!draggedContentRanges.isEmpty() && !paintSelectedTextOnly && !paintNonSelectedTextOnly) {
557         // FIXME: Painting with text effects ranges currently only works if we're not also painting the selection.
558         // In the future, we may want to support this capability, but in the meantime, this isn't required by anything.
559         unsigned currentEnd = 0;
560         for (size_t index = 0; index < draggedContentRanges.size(); ++index) {
561             unsigned previousEnd = index ? std::min(draggedContentRanges[index - 1].second, length) : 0;
562             unsigned currentStart = draggedContentRanges[index].first - m_start;
563             currentEnd = std::min(draggedContentRanges[index].second - m_start, length);
564
565             if (previousEnd < currentStart)
566                 textPainter.paintRange(textRun, boxRect, textOrigin, previousEnd, currentStart);
567
568             if (currentStart < currentEnd) {
569                 context.save();
570                 context.setAlpha(0.25);
571                 textPainter.paintRange(textRun, boxRect, textOrigin, currentStart, currentEnd);
572                 context.restore();
573             }
574         }
575         if (currentEnd < length)
576             textPainter.paintRange(textRun, boxRect, textOrigin, currentEnd, length);
577     } else
578         textPainter.paint(textRun, length, boxRect, textOrigin, selectionStart, selectionEnd, paintSelectedTextOnly, paintSelectedTextSeparately, paintNonSelectedTextOnly);
579
580     // Paint decorations
581     TextDecoration textDecorations = lineStyle.textDecorationsInEffect();
582     if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) {
583         FloatRect textDecorationSelectionClipOutRect;
584         if ((paintInfo.paintBehavior & PaintBehaviorExcludeSelection) && selectionStart < selectionEnd && selectionEnd <= length) {
585             textDecorationSelectionClipOutRect = logicalOverflowRect();
586             textDecorationSelectionClipOutRect.moveBy(localPaintOffset);
587             float logicalWidthBeforeRange;
588             float logicalWidthAfterRange;
589             float logicalSelectionWidth = font.widthOfTextRange(textRun, selectionStart, selectionEnd, nullptr, &logicalWidthBeforeRange, &logicalWidthAfterRange);
590             // FIXME: Do we need to handle vertical bottom to top text?
591             if (!isHorizontal()) {
592                 textDecorationSelectionClipOutRect.move(0, logicalWidthBeforeRange);
593                 textDecorationSelectionClipOutRect.setHeight(logicalSelectionWidth);
594             } else if (direction() == RTL) {
595                 textDecorationSelectionClipOutRect.move(logicalWidthAfterRange, 0);
596                 textDecorationSelectionClipOutRect.setWidth(logicalSelectionWidth);
597             } else {
598                 textDecorationSelectionClipOutRect.move(logicalWidthBeforeRange, 0);
599                 textDecorationSelectionClipOutRect.setWidth(logicalSelectionWidth);
600             }
601         }
602         paintDecoration(context, textRun, textOrigin, boxRect, textDecorations, textPaintStyle, textShadow, textDecorationSelectionClipOutRect);
603     }
604
605     if (paintInfo.phase == PaintPhaseForeground) {
606         paintDocumentMarkers(context, localPaintOffset, false);
607
608         if (useCustomUnderlines)
609             paintCompositionUnderlines(context, boxOrigin);
610     }
611     
612     if (shouldRotate)
613         context.concatCTM(rotation(boxRect, Counterclockwise));
614 }
615
616 unsigned InlineTextBox::clampedOffset(unsigned x) const
617 {
618     unsigned offset = std::max(std::min(x, m_start + m_len), m_start) - m_start;
619     if (m_truncation == cFullTruncation)
620         return offset;
621     if (m_truncation != cNoTruncation)
622         offset = std::min<unsigned>(offset, m_truncation);
623     else if (offset == m_len) {
624         // Fix up the offset if we are combined text or have a hyphen because we manage these embellishments.
625         // That is, they are not reflected in renderer().text(). We treat combined text as a single unit.
626         // We also treat the last codepoint in this box and the hyphen as a single unit.
627         if (auto* combinedText = this->combinedText())
628             offset = combinedText->combinedStringForRendering().length();
629         else if (hasHyphen())
630             offset += lineStyle().hyphenString().length();
631     }
632     return offset;
633 }
634
635 std::pair<unsigned, unsigned> InlineTextBox::selectionStartEnd() const
636 {
637     auto selectionState = renderer().selectionState();
638     if (selectionState == RenderObject::SelectionInside)
639         return { 0, clampedOffset(m_start + m_len) };
640     
641     auto start = renderer().view().selection().startPosition();
642     auto end = renderer().view().selection().endPosition();
643     if (selectionState == RenderObject::SelectionStart)
644         end = renderer().textLength();
645     else if (selectionState == RenderObject::SelectionEnd)
646         start = 0;
647     return { clampedOffset(start), clampedOffset(end) };
648 }
649
650 void InlineTextBox::paintSelection(GraphicsContext& context, const LayoutPoint& paintOffset, const Color& textColor)
651 {
652 #if ENABLE(TEXT_SELECTION)
653     if (context.paintingDisabled())
654         return;
655
656     // See if we have a selection to paint at all.
657     unsigned selectionStart;
658     unsigned selectionEnd;
659     std::tie(selectionStart, selectionEnd) = selectionStartEnd();
660     if (selectionStart >= selectionEnd)
661         return;
662
663     Color c = renderer().selectionBackgroundColor();
664     if (!c.isValid() || c.alpha() == 0)
665         return;
666
667     // If the text color ends up being the same as the selection background, invert the selection
668     // background.
669     if (textColor == c)
670         c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
671
672     GraphicsContextStateSaver stateSaver(context);
673     updateGraphicsContext(context, TextPaintStyle(c)); // Don't draw text at all!
674
675     // Note that if the text is truncated, we let the thing being painted in the truncation
676     // draw its own highlight.
677     auto selectionRect = localSelectionRectWithClampedPositions(selectionStart, selectionEnd);
678     selectionRect.moveBy(paintOffset);
679
680     // FIXME: Support painting combined text.
681     context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), isLeftToRightDirection()), c);
682 #else
683     UNUSED_PARAM(context);
684     UNUSED_PARAM(paintOffset);
685     UNUSED_PARAM(textColor);
686 #endif
687 }
688
689 inline void InlineTextBox::paintTextSubrangeBackground(GraphicsContext& context, const LayoutPoint& paintOffset, const Color& color, unsigned startOffset, unsigned endOffset)
690 {
691     startOffset = clampedOffset(startOffset);
692     endOffset = clampedOffset(endOffset);
693     if (startOffset >= endOffset)
694         return;
695
696     GraphicsContextStateSaver stateSaver { context };
697     updateGraphicsContext(context, TextPaintStyle { color }); // Don't draw text at all!
698
699     auto selectionRect = localSelectionRectWithClampedPositions(startOffset, endOffset);
700     selectionRect.moveBy(paintOffset);
701
702     // FIXME: Support painting combined text.
703     context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), isLeftToRightDirection()), color);
704 }
705
706 void InlineTextBox::paintCompositionBackground(GraphicsContext& context, const LayoutPoint& paintOffset)
707 {
708     paintTextSubrangeBackground(context, paintOffset, renderer().frame().editor().compositionStart(), renderer().frame().editor().compositionEnd(), Color::compositionFill);
709 }
710
711 void InlineTextBox::paintTextMatchMarker(GraphicsContext& context, const LayoutPoint& paintOffset, const MarkerSubrange& subrange, bool isActiveMatch)
712 {
713     if (!renderer().frame().editor().markedTextMatchesAreHighlighted())
714         return;
715     auto highlightColor = isActiveMatch ? renderer().theme().platformActiveTextSearchHighlightColor() : renderer().theme().platformInactiveTextSearchHighlightColor();
716     paintTextSubrangeBackground(context, paintOffset, highlightColor, subrange.startOffset, subrange.endOffset);
717 }
718
719 static inline void mirrorRTLSegment(float logicalWidth, TextDirection direction, float& start, float width)
720 {
721     if (direction == LTR)
722         return;
723     start = logicalWidth - width - start;
724 }
725
726 void InlineTextBox::paintDecoration(GraphicsContext& context, const TextRun& textRun, const FloatPoint& textOrigin,
727     const FloatRect& boxRect, TextDecoration decoration, TextPaintStyle textPaintStyle, const ShadowData* shadow, const FloatRect& clipOutRect)
728 {
729     if (m_truncation == cFullTruncation)
730         return;
731
732     updateGraphicsContext(context, textPaintStyle);
733
734     bool isCombinedText = combinedText();
735     if (isCombinedText)
736         context.concatCTM(rotation(boxRect, Clockwise));
737
738     float start = 0;
739     float width = m_logicalWidth;
740     if (m_truncation != cNoTruncation) {
741         width = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
742         mirrorRTLSegment(m_logicalWidth, direction(), start, width);
743     }
744
745     TextDecorationPainter decorationPainter(context, decoration, renderer(), isFirstLine());
746     decorationPainter.setInlineTextBox(this);
747     decorationPainter.setFont(lineFont());
748     decorationPainter.setWidth(width);
749     decorationPainter.setBaseline(lineStyle().fontMetrics().ascent());
750     decorationPainter.setIsHorizontal(isHorizontal());
751     decorationPainter.addTextShadow(shadow);
752
753     FloatPoint localOrigin = boxRect.location();
754     localOrigin.move(start, 0);
755
756     {
757         GraphicsContextStateSaver stateSaver { context, false };
758         if (!clipOutRect.isEmpty()) {
759             stateSaver.save();
760             context.clipOut(clipOutRect);
761         }
762         decorationPainter.paintTextDecoration(textRun, textOrigin, localOrigin);
763     }
764
765     if (isCombinedText)
766         context.concatCTM(rotation(boxRect, Counterclockwise));
767 }
768
769 void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const LayoutPoint& paintOffset, const MarkerSubrange& subrange)
770 {
771     // Never print spelling/grammar markers (5327887)
772     if (renderer().document().printing())
773         return;
774
775     if (m_truncation == cFullTruncation)
776         return;
777
778     // Determine whether we need to measure text
779     bool markerSpansWholeBox = true;
780     if (m_start <= subrange.startOffset)
781         markerSpansWholeBox = false;
782     if ((end() + 1) != subrange.endOffset) // End points at the last char, not past it
783         markerSpansWholeBox = false;
784     if (m_truncation != cNoTruncation)
785         markerSpansWholeBox = false;
786
787     FloatPoint location;
788     float width;
789     if (markerSpansWholeBox) {
790         location = locationIncludingFlipping();
791         width = m_logicalWidth;
792     } else {
793         unsigned startPosition = clampedOffset(subrange.startOffset);
794         unsigned endPosition = clampedOffset(subrange.endOffset);
795
796         // We round to the nearest enclosing integral rect to avoid truncating the dot decoration on Cocoa platforms.
797         // FIXME: Find a better place to ensure that the Cocoa dots are not truncated.
798         auto selectionRect = localSelectionRectWithClampedPositions(startPosition, endPosition, SelectionRectRounding::EnclosingIntRect);
799         location = selectionRect.location();
800         width = selectionRect.width();
801     }
802     location.moveBy(paintOffset);
803
804     auto lineStyleForSubrangeType = [] (MarkerSubrange::Type type) {
805         switch (type) {
806         case MarkerSubrange::SpellingError:
807             return GraphicsContext::DocumentMarkerSpellingLineStyle;
808         case MarkerSubrange::GrammarError:
809             return GraphicsContext::DocumentMarkerGrammarLineStyle;
810         case MarkerSubrange::Correction:
811             return GraphicsContext::DocumentMarkerAutocorrectionReplacementLineStyle;
812         case MarkerSubrange::DictationAlternatives:
813             return GraphicsContext::DocumentMarkerDictationAlternativesLineStyle;
814 #if PLATFORM(IOS)
815         case MarkerSubrange::DictationPhraseWithAlternatives:
816             // FIXME: Rename TextCheckingDictationPhraseWithAlternativesLineStyle and remove the PLATFORM(IOS)-guard.
817             return GraphicsContext::TextCheckingDictationPhraseWithAlternativesLineStyle;
818 #endif
819         default:
820             ASSERT_NOT_REACHED();
821             return GraphicsContext::DocumentMarkerSpellingLineStyle;
822         }
823     };
824
825     // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to
826     // make sure to fit within those bounds.  This means the top pixel(s) of the underline will overlap the
827     // bottom pixel(s) of the glyphs in smaller font sizes.  The alternatives are to increase the line spacing (bad!!)
828     // or decrease the underline thickness.  The overlap is actually the most useful, and matches what AppKit does.
829     // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so
830     // we pin to two pixels under the baseline.
831     int lineThickness = cMisspellingLineThickness;
832     int baseline = lineStyle().fontMetrics().ascent();
833     int descent = logicalHeight() - baseline;
834     int underlineOffset;
835     if (descent <= (2 + lineThickness)) {
836         // Place the underline at the very bottom of the text in small/medium fonts.
837         underlineOffset = logicalHeight() - lineThickness;
838     } else {
839         // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
840         underlineOffset = baseline + 2;
841     }
842     // FIXME: Support painting combined text.
843     location.move(0, underlineOffset);
844     context.drawLineForDocumentMarker(location, width, lineStyleForSubrangeType(subrange.type));
845 }
846
847 void InlineTextBox::paintDocumentMarkers(GraphicsContext& context, const LayoutPoint& paintOffset, bool background)
848 {
849     if (!renderer().textNode())
850         return;
851
852     Vector<RenderedDocumentMarker*> markers = renderer().document().markers().markersFor(renderer().textNode());
853
854     auto markerTypeForSubrangeType = [] (DocumentMarker::MarkerType type) {
855         switch (type) {
856         case DocumentMarker::Spelling:
857             return MarkerSubrange::SpellingError;
858         case DocumentMarker::Grammar:
859             return MarkerSubrange::GrammarError;
860         case DocumentMarker::CorrectionIndicator:
861             return MarkerSubrange::Correction;
862         case DocumentMarker::TextMatch:
863             return MarkerSubrange::TextMatch;
864         case DocumentMarker::DictationAlternatives:
865             return MarkerSubrange::DictationAlternatives;
866 #if PLATFORM(IOS)
867         case DocumentMarker::DictationPhraseWithAlternatives:
868             return MarkerSubrange::DictationPhraseWithAlternatives;
869 #endif
870         default:
871             return MarkerSubrange::Unmarked;
872         }
873     };
874
875     Vector<MarkerSubrange> subranges;
876     subranges.reserveInitialCapacity(markers.size());
877
878     // Give any document markers that touch this run a chance to draw before the text has been drawn.
879     // Note end() points at the last char, not one past it like endOffset and ranges do.
880     for (auto* marker : markers) {
881         // Paint either the background markers or the foreground markers, but not both
882         switch (marker->type()) {
883             case DocumentMarker::Grammar:
884             case DocumentMarker::Spelling:
885             case DocumentMarker::CorrectionIndicator:
886             case DocumentMarker::Replacement:
887             case DocumentMarker::DictationAlternatives:
888 #if PLATFORM(IOS)
889             // FIXME: Remove the PLATFORM(IOS)-guard.
890             case DocumentMarker::DictationPhraseWithAlternatives:
891 #endif
892                 if (background)
893                     continue;
894                 break;
895             case DocumentMarker::TextMatch:
896 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
897             case DocumentMarker::TelephoneNumber:
898 #endif
899                 if (!background)
900                     continue;
901                 break;
902             default:
903                 continue;
904         }
905
906         if (marker->endOffset() <= start())
907             // marker is completely before this run.  This might be a marker that sits before the
908             // first run we draw, or markers that were within runs we skipped due to truncation.
909             continue;
910         
911         if (marker->startOffset() > end())
912             // marker is completely after this run, bail.  A later run will paint it.
913             break;
914         
915         // marker intersects this run.  Paint it.
916         switch (marker->type()) {
917             case DocumentMarker::Spelling:
918             case DocumentMarker::CorrectionIndicator:
919             case DocumentMarker::DictationAlternatives:
920             case DocumentMarker::Grammar:
921 #if PLATFORM(IOS)
922             // FIXME: See <rdar://problem/8933352>. Also, remove the PLATFORM(IOS)-guard.
923             case DocumentMarker::DictationPhraseWithAlternatives:
924 #endif
925             case DocumentMarker::TextMatch:
926                 subranges.uncheckedAppend({ marker->startOffset(), marker->endOffset(), markerTypeForSubrangeType(marker->type()), marker });
927                 break;
928             case DocumentMarker::Replacement:
929                 break;
930 #if ENABLE(TELEPHONE_NUMBER_DETECTION)
931             case DocumentMarker::TelephoneNumber:
932                 break;
933 #endif
934             default:
935                 ASSERT_NOT_REACHED();
936         }
937     }
938
939     for (auto& subrange : subdivide(subranges, OverlapStrategy::Frontmost)) {
940         if (subrange.type == MarkerSubrange::TextMatch)
941             paintTextMatchMarker(context, paintOffset, subrange, subrange.marker->isActiveMatch());
942         else
943             paintDocumentMarker(context, paintOffset, subrange);
944     }
945 }
946
947 void InlineTextBox::paintCompositionUnderlines(GraphicsContext& context, const FloatPoint& boxOrigin) const
948 {
949     if (m_truncation == cFullTruncation)
950         return;
951
952     for (auto& underline : renderer().frame().editor().customCompositionUnderlines()) {
953         if (underline.endOffset <= m_start) {
954             // Underline is completely before this run. This might be an underline that sits
955             // before the first run we draw, or underlines that were within runs we skipped
956             // due to truncation.
957             continue;
958         }
959
960         if (underline.startOffset > end())
961             break; // Underline is completely after this run, bail. A later run will paint it.
962
963         // Underline intersects this run. Paint it.
964         paintCompositionUnderline(context, boxOrigin, underline);
965
966         if (underline.endOffset > end() + 1)
967             break; // Underline also runs into the next run. Bail now, no more marker advancement.
968     }
969 }
970
971 void InlineTextBox::paintCompositionUnderline(GraphicsContext& context, const FloatPoint& boxOrigin, const CompositionUnderline& underline) const
972 {
973     if (m_truncation == cFullTruncation)
974         return;
975     
976     float start = 0; // start of line to draw, relative to tx
977     float width = m_logicalWidth; // how much line to draw
978     bool useWholeWidth = true;
979     unsigned paintStart = m_start;
980     unsigned paintEnd = end() + 1; // end points at the last char, not past it
981     if (paintStart <= underline.startOffset) {
982         paintStart = underline.startOffset;
983         useWholeWidth = false;
984         start = renderer().width(m_start, paintStart - m_start, textPos(), isFirstLine());
985     }
986     if (paintEnd != underline.endOffset) {      // end points at the last char, not past it
987         paintEnd = std::min(paintEnd, (unsigned)underline.endOffset);
988         useWholeWidth = false;
989     }
990     if (m_truncation != cNoTruncation) {
991         paintEnd = std::min(paintEnd, (unsigned)m_start + m_truncation);
992         useWholeWidth = false;
993     }
994     if (!useWholeWidth) {
995         width = renderer().width(paintStart, paintEnd - paintStart, textPos() + start, isFirstLine());
996         mirrorRTLSegment(m_logicalWidth, direction(), start, width);
997     }
998
999     // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
1000     // All other marked text underlines are 1px thick.
1001     // If there's not enough space the underline will touch or overlap characters.
1002     int lineThickness = 1;
1003     int baseline = lineStyle().fontMetrics().ascent();
1004     if (underline.thick && logicalHeight() - baseline >= 2)
1005         lineThickness = 2;
1006
1007     // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
1008     // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
1009     start += 1;
1010     width -= 2;
1011
1012     context.setStrokeColor(underline.compositionUnderlineColor == CompositionUnderlineColor::TextColor ? renderer().style().visitedDependentColor(CSSPropertyWebkitTextFillColor) : underline.color);
1013     context.setStrokeThickness(lineThickness);
1014     context.drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, renderer().document().printing());
1015 }
1016
1017 int InlineTextBox::caretMinOffset() const
1018 {
1019     return m_start;
1020 }
1021
1022 int InlineTextBox::caretMaxOffset() const
1023 {
1024     return m_start + m_len;
1025 }
1026
1027 float InlineTextBox::textPos() const
1028 {
1029     // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset
1030     // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width.
1031     if (logicalLeft() == 0)
1032         return 0;
1033     return logicalLeft() - root().logicalLeft();
1034 }
1035
1036 int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs) const
1037 {
1038     if (isLineBreak())
1039         return 0;
1040     if (lineOffset - logicalLeft() > logicalWidth())
1041         return isLeftToRightDirection() ? len() : 0;
1042     if (lineOffset - logicalLeft() < 0)
1043         return isLeftToRightDirection() ? 0 : len();
1044     bool ignoreCombinedText = true;
1045     bool ignoreHyphen = true;
1046     auto text = this->text(ignoreCombinedText, ignoreHyphen);
1047     return lineFont().offsetForPosition(createTextRun(text), lineOffset - logicalLeft(), includePartialGlyphs);
1048 }
1049
1050 float InlineTextBox::positionForOffset(unsigned offset) const
1051 {
1052     ASSERT(offset >= m_start);
1053     ASSERT(offset <= m_start + len());
1054
1055     if (isLineBreak())
1056         return logicalLeft();
1057
1058     unsigned startOffset;
1059     unsigned endOffset;
1060     if (isLeftToRightDirection()) {
1061         startOffset = 0;
1062         endOffset = clampedOffset(offset);
1063     } else {
1064         startOffset = clampedOffset(offset);
1065         endOffset = m_len;
1066     }
1067
1068     // FIXME: Do we need to add rightBearing here?
1069     LayoutRect selectionRect = LayoutRect(logicalLeft(), 0, 0, 0);
1070     bool ignoreCombinedText = true;
1071     bool ignoreHyphen = true;
1072     auto text = this->text(ignoreCombinedText, ignoreHyphen);
1073     TextRun textRun = createTextRun(text);
1074     lineFont().adjustSelectionRectForText(textRun, selectionRect, startOffset, endOffset);
1075     return snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()).maxX();
1076 }
1077
1078 TextRun InlineTextBox::createTextRun(String& string) const
1079 {
1080     const auto& style = lineStyle();
1081     TextRun textRun { string, textPos(), expansion(), expansionBehavior(), direction(), dirOverride() || style.rtlOrdering() == VisualOrder, !renderer().canUseSimpleFontCodePath() };
1082     textRun.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
1083     return textRun;
1084 }
1085
1086 String InlineTextBox::text(bool ignoreCombinedText, bool ignoreHyphen) const
1087 {
1088     if (auto* combinedText = this->combinedText()) {
1089         if (ignoreCombinedText)
1090             return renderer().text()->substring(m_start, m_len);
1091         return combinedText->combinedStringForRendering();
1092     }
1093     if (hasHyphen()) {
1094         if (ignoreHyphen)
1095             return renderer().text()->substring(m_start, m_len);
1096         return makeString(StringView(renderer().text()).substring(m_start, m_len), lineStyle().hyphenString());
1097     }
1098     return renderer().text()->substring(m_start, m_len);
1099 }
1100
1101 inline const RenderCombineText* InlineTextBox::combinedText() const
1102 {
1103     return lineStyle().hasTextCombine() && is<RenderCombineText>(renderer()) && downcast<RenderCombineText>(renderer()).isCombined() ? &downcast<RenderCombineText>(renderer()) : nullptr;
1104 }
1105
1106 ExpansionBehavior InlineTextBox::expansionBehavior() const
1107 {
1108     ExpansionBehavior leadingBehavior;
1109     if (forceLeadingExpansion())
1110         leadingBehavior = ForceLeadingExpansion;
1111     else if (canHaveLeadingExpansion())
1112         leadingBehavior = AllowLeadingExpansion;
1113     else
1114         leadingBehavior = ForbidLeadingExpansion;
1115
1116     ExpansionBehavior trailingBehavior;
1117     if (forceTrailingExpansion())
1118         trailingBehavior = ForceTrailingExpansion;
1119     else if (expansion() && nextLeafChild() && !nextLeafChild()->isLineBreak())
1120         trailingBehavior = AllowTrailingExpansion;
1121     else
1122         trailingBehavior = ForbidTrailingExpansion;
1123
1124     return leadingBehavior | trailingBehavior;
1125 }
1126
1127 #if ENABLE(TREE_DEBUGGING)
1128
1129 const char* InlineTextBox::boxName() const
1130 {
1131     return "InlineTextBox";
1132 }
1133
1134 void InlineTextBox::outputLineBox(TextStream& stream, bool mark, int depth) const
1135 {
1136     stream << "-------- " << (isDirty() ? "D" : "-") << "-";
1137
1138     int printedCharacters = 0;
1139     if (mark) {
1140         stream << "*";
1141         ++printedCharacters;
1142     }
1143     while (++printedCharacters <= depth * 2)
1144         stream << " ";
1145
1146     String value = renderer().text();
1147     value = value.substring(start(), len());
1148     value.replaceWithLiteral('\\', "\\\\");
1149     value.replaceWithLiteral('\n', "\\n");
1150     stream << boxName() << " " << FloatRect(x(), y(), width(), height()) << " (" << this << ") renderer->(" << &renderer() << ") run(" << start() << ", " << start() + len() << ") \"" << value.utf8().data() << "\"";
1151     stream.nextLine();
1152 }
1153
1154 #endif
1155
1156 } // namespace WebCore