• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * (C) 1999 Lars Knoll (knoll@kde.org)
3  * (C) 2000 Dirk Mueller (mueller@kde.org)
4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 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 "core/rendering/InlineTextBox.h"
25 
26 #include "core/dom/Document.h"
27 #include "core/dom/DocumentMarkerController.h"
28 #include "core/dom/RenderedDocumentMarker.h"
29 #include "core/dom/Text.h"
30 #include "core/editing/CompositionUnderline.h"
31 #include "core/editing/CompositionUnderlineRangeFilter.h"
32 #include "core/editing/Editor.h"
33 #include "core/editing/InputMethodController.h"
34 #include "core/frame/LocalFrame.h"
35 #include "core/page/Page.h"
36 #include "core/frame/Settings.h"
37 #include "core/rendering/AbstractInlineTextBox.h"
38 #include "core/rendering/EllipsisBox.h"
39 #include "core/rendering/HitTestResult.h"
40 #include "core/rendering/PaintInfo.h"
41 #include "core/rendering/RenderBR.h"
42 #include "core/rendering/RenderBlock.h"
43 #include "core/rendering/RenderCombineText.h"
44 #include "core/rendering/RenderRubyRun.h"
45 #include "core/rendering/RenderRubyText.h"
46 #include "core/rendering/RenderTheme.h"
47 #include "core/rendering/style/ShadowList.h"
48 #include "core/rendering/svg/SVGTextRunRenderingContext.h"
49 #include "platform/fonts/FontCache.h"
50 #include "platform/fonts/GlyphBuffer.h"
51 #include "platform/fonts/WidthIterator.h"
52 #include "platform/graphics/DrawLooperBuilder.h"
53 #include "platform/graphics/GraphicsContextStateSaver.h"
54 #include "wtf/Vector.h"
55 #include "wtf/text/CString.h"
56 #include "wtf/text/StringBuilder.h"
57 
58 #include <algorithm>
59 
60 using namespace std;
61 
62 namespace WebCore {
63 
64 struct SameSizeAsInlineTextBox : public InlineBox {
65     unsigned variables[1];
66     unsigned short variables2[2];
67     void* pointers[2];
68 };
69 
70 COMPILE_ASSERT(sizeof(InlineTextBox) == sizeof(SameSizeAsInlineTextBox), InlineTextBox_should_stay_small);
71 
72 typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap;
73 static InlineTextBoxOverflowMap* gTextBoxesWithOverflow;
74 
75 static const int misspellingLineThickness = 3;
76 
destroy()77 void InlineTextBox::destroy()
78 {
79     AbstractInlineTextBox::willDestroy(this);
80 
81     if (!knownToHaveNoOverflow() && gTextBoxesWithOverflow)
82         gTextBoxesWithOverflow->remove(this);
83     InlineBox::destroy();
84 }
85 
markDirty()86 void InlineTextBox::markDirty()
87 {
88     m_len = 0;
89     m_start = 0;
90     InlineBox::markDirty();
91 }
92 
logicalOverflowRect() const93 LayoutRect InlineTextBox::logicalOverflowRect() const
94 {
95     if (knownToHaveNoOverflow() || !gTextBoxesWithOverflow)
96         return enclosingIntRect(logicalFrameRect());
97     return gTextBoxesWithOverflow->get(this);
98 }
99 
setLogicalOverflowRect(const LayoutRect & rect)100 void InlineTextBox::setLogicalOverflowRect(const LayoutRect& rect)
101 {
102     ASSERT(!knownToHaveNoOverflow());
103     if (!gTextBoxesWithOverflow)
104         gTextBoxesWithOverflow = new InlineTextBoxOverflowMap;
105     gTextBoxesWithOverflow->add(this, rect);
106 }
107 
baselinePosition(FontBaseline baselineType) const108 int InlineTextBox::baselinePosition(FontBaseline baselineType) const
109 {
110     if (!isText() || !parent())
111         return 0;
112     if (parent()->renderer() == renderer().parent())
113         return parent()->baselinePosition(baselineType);
114     return toRenderBoxModelObject(renderer().parent())->baselinePosition(baselineType, isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
115 }
116 
lineHeight() const117 LayoutUnit InlineTextBox::lineHeight() const
118 {
119     if (!isText() || !renderer().parent())
120         return 0;
121     if (renderer().isBR())
122         return toRenderBR(renderer()).lineHeight(isFirstLineStyle());
123     if (parent()->renderer() == renderer().parent())
124         return parent()->lineHeight();
125     return toRenderBoxModelObject(renderer().parent())->lineHeight(isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
126 }
127 
selectionTop()128 LayoutUnit InlineTextBox::selectionTop()
129 {
130     return root().selectionTop();
131 }
132 
selectionBottom()133 LayoutUnit InlineTextBox::selectionBottom()
134 {
135     return root().selectionBottom();
136 }
137 
selectionHeight()138 LayoutUnit InlineTextBox::selectionHeight()
139 {
140     return root().selectionHeight();
141 }
142 
isSelected(int startPos,int endPos) const143 bool InlineTextBox::isSelected(int startPos, int endPos) const
144 {
145     int sPos = max(startPos - m_start, 0);
146     // The position after a hard line break is considered to be past its end.
147     // See the corresponding code in InlineTextBox::selectionState.
148     int ePos = min(endPos - m_start, int(m_len) + (isLineBreak() ? 0 : 1));
149     return (sPos < ePos);
150 }
151 
selectionState()152 RenderObject::SelectionState InlineTextBox::selectionState()
153 {
154     RenderObject::SelectionState state = renderer().selectionState();
155     if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
156         int startPos, endPos;
157         renderer().selectionStartEnd(startPos, endPos);
158         // The position after a hard line break is considered to be past its end.
159         // See the corresponding code in InlineTextBox::isSelected.
160         int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0);
161 
162         // FIXME: Remove -webkit-line-break: LineBreakAfterWhiteSpace.
163         int endOfLineAdjustmentForCSSLineBreak = renderer().style()->lineBreak() == LineBreakAfterWhiteSpace ? -1 : 0;
164         bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos <= m_start + m_len + endOfLineAdjustmentForCSSLineBreak);
165         bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
166         if (start && end)
167             state = RenderObject::SelectionBoth;
168         else if (start)
169             state = RenderObject::SelectionStart;
170         else if (end)
171             state = RenderObject::SelectionEnd;
172         else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
173                  (state == RenderObject::SelectionStart || endPos > lastSelectable))
174             state = RenderObject::SelectionInside;
175         else if (state == RenderObject::SelectionBoth)
176             state = RenderObject::SelectionNone;
177     }
178 
179     // If there are ellipsis following, make sure their selection is updated.
180     if (m_truncation != cNoTruncation && root().ellipsisBox()) {
181         EllipsisBox* ellipsis = root().ellipsisBox();
182         if (state != RenderObject::SelectionNone) {
183             int start, end;
184             selectionStartEnd(start, end);
185             // The ellipsis should be considered to be selected if the end of
186             // the selection is past the beginning of the truncation and the
187             // beginning of the selection is before or at the beginning of the
188             // truncation.
189             ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ?
190                 RenderObject::SelectionInside : RenderObject::SelectionNone);
191         } else
192             ellipsis->setSelectionState(RenderObject::SelectionNone);
193     }
194 
195     return state;
196 }
197 
localSelectionRect(int startPos,int endPos)198 LayoutRect InlineTextBox::localSelectionRect(int startPos, int endPos)
199 {
200     int sPos = max(startPos - m_start, 0);
201     int ePos = min(endPos - m_start, (int)m_len);
202 
203     if (sPos > ePos)
204         return LayoutRect();
205 
206     FontCachePurgePreventer fontCachePurgePreventer;
207 
208     LayoutUnit selTop = selectionTop();
209     LayoutUnit selHeight = selectionHeight();
210     RenderStyle* styleToUse = textRenderer().style(isFirstLineStyle());
211     const Font& font = styleToUse->font();
212 
213     StringBuilder charactersWithHyphen;
214     bool respectHyphen = ePos == m_len && hasHyphen();
215     TextRun textRun = constructTextRun(styleToUse, font, respectHyphen ? &charactersWithHyphen : 0);
216 
217     FloatPoint startingPoint = FloatPoint(logicalLeft(), selTop.toFloat());
218     LayoutRect r;
219     if (sPos || ePos != static_cast<int>(m_len))
220         r = enclosingIntRect(font.selectionRectForText(textRun, startingPoint, selHeight, sPos, ePos));
221     else // Avoid computing the font width when the entire line box is selected as an optimization.
222         r = enclosingIntRect(FloatRect(startingPoint, FloatSize(m_logicalWidth, selHeight.toFloat())));
223 
224     LayoutUnit logicalWidth = r.width();
225     if (r.x() > logicalRight())
226         logicalWidth  = 0;
227     else if (r.maxX() > logicalRight())
228         logicalWidth = logicalRight() - r.x();
229 
230     LayoutPoint topPoint = isHorizontal() ? LayoutPoint(r.x(), selTop) : LayoutPoint(selTop, r.x());
231     LayoutUnit width = isHorizontal() ? logicalWidth : selHeight;
232     LayoutUnit height = isHorizontal() ? selHeight : logicalWidth;
233 
234     return LayoutRect(topPoint, LayoutSize(width, height));
235 }
236 
deleteLine()237 void InlineTextBox::deleteLine()
238 {
239     toRenderText(renderer()).removeTextBox(this);
240     destroy();
241 }
242 
extractLine()243 void InlineTextBox::extractLine()
244 {
245     if (extracted())
246         return;
247 
248     toRenderText(renderer()).extractTextBox(this);
249 }
250 
attachLine()251 void InlineTextBox::attachLine()
252 {
253     if (!extracted())
254         return;
255 
256     toRenderText(renderer()).attachTextBox(this);
257 }
258 
placeEllipsisBox(bool flowIsLTR,float visibleLeftEdge,float visibleRightEdge,float ellipsisWidth,float & truncatedWidth,bool & foundBox)259 float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, float &truncatedWidth, bool& foundBox)
260 {
261     if (foundBox) {
262         m_truncation = cFullTruncation;
263         return -1;
264     }
265 
266     // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
267     float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
268 
269     // Criteria for full truncation:
270     // LTR: the left edge of the ellipsis is to the left of our text run.
271     // RTL: the right edge of the ellipsis is to the right of our text run.
272     bool ltrFullTruncation = flowIsLTR && ellipsisX <= logicalLeft();
273     bool rtlFullTruncation = !flowIsLTR && ellipsisX >= logicalLeft() + logicalWidth();
274     if (ltrFullTruncation || rtlFullTruncation) {
275         // Too far.  Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
276         m_truncation = cFullTruncation;
277         foundBox = true;
278         return -1;
279     }
280 
281     bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < logicalRight());
282     bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > logicalLeft());
283     if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
284         foundBox = true;
285 
286         // The inline box may have different directionality than it's parent.  Since truncation
287         // behavior depends both on both the parent and the inline block's directionality, we
288         // must keep track of these separately.
289         bool ltr = isLeftToRightDirection();
290         if (ltr != flowIsLTR) {
291             // Width in pixels of the visible portion of the box, excluding the ellipsis.
292             int visibleBoxWidth = visibleRightEdge - visibleLeftEdge  - ellipsisWidth;
293             ellipsisX = ltr ? logicalLeft() + visibleBoxWidth : logicalRight() - visibleBoxWidth;
294         }
295 
296         int offset = offsetForPosition(ellipsisX, false);
297         if (offset == 0) {
298             // No characters should be rendered.  Set ourselves to full truncation and place the ellipsis at the min of our start
299             // and the ellipsis edge.
300             m_truncation = cFullTruncation;
301             truncatedWidth += ellipsisWidth;
302             return min(ellipsisX, logicalLeft());
303         }
304 
305         // Set the truncation index on the text run.
306         m_truncation = offset;
307 
308         // If we got here that means that we were only partially truncated and we need to return the pixel offset at which
309         // to place the ellipsis.
310         float widthOfVisibleText = toRenderText(renderer()).width(m_start, offset, textPos(), flowIsLTR ? LTR : RTL, isFirstLineStyle());
311 
312         // The ellipsis needs to be placed just after the last visible character.
313         // Where "after" is defined by the flow directionality, not the inline
314         // box directionality.
315         // e.g. In the case of an LTR inline box truncated in an RTL flow then we can
316         // have a situation such as |Hello| -> |...He|
317         truncatedWidth += widthOfVisibleText + ellipsisWidth;
318         if (flowIsLTR)
319             return logicalLeft() + widthOfVisibleText;
320         else
321             return logicalRight() - widthOfVisibleText - ellipsisWidth;
322     }
323     truncatedWidth += logicalWidth();
324     return -1;
325 }
326 
correctedTextColor(Color textColor,Color backgroundColor)327 Color correctedTextColor(Color textColor, Color backgroundColor)
328 {
329     // Adjust the text color if it is too close to the background color,
330     // by darkening or lightening it to move it further away.
331 
332     int d = differenceSquared(textColor, backgroundColor);
333     // semi-arbitrarily chose 65025 (255^2) value here after a few tests;
334     if (d > 65025) {
335         return textColor;
336     }
337 
338     int distanceFromWhite = differenceSquared(textColor, Color::white);
339     int distanceFromBlack = differenceSquared(textColor, Color::black);
340 
341     if (distanceFromWhite < distanceFromBlack) {
342         return textColor.dark();
343     }
344 
345     return textColor.light();
346 }
347 
updateGraphicsContext(GraphicsContext * context,const Color & fillColor,const Color & strokeColor,float strokeThickness)348 void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness)
349 {
350     TextDrawingModeFlags mode = context->textDrawingMode();
351     if (strokeThickness > 0) {
352         TextDrawingModeFlags newMode = mode | TextModeStroke;
353         if (mode != newMode) {
354             context->setTextDrawingMode(newMode);
355             mode = newMode;
356         }
357     }
358 
359     if (mode & TextModeFill && fillColor != context->fillColor())
360         context->setFillColor(fillColor);
361 
362     if (mode & TextModeStroke) {
363         if (strokeColor != context->strokeColor())
364             context->setStrokeColor(strokeColor);
365         if (strokeThickness != context->strokeThickness())
366             context->setStrokeThickness(strokeThickness);
367     }
368 }
369 
isLineBreak() const370 bool InlineTextBox::isLineBreak() const
371 {
372     return renderer().isBR() || (renderer().style()->preserveNewline() && len() == 1 && (*textRenderer().text().impl())[start()] == '\n');
373 }
374 
nodeAtPoint(const HitTestRequest & request,HitTestResult & result,const HitTestLocation & locationInContainer,const LayoutPoint & accumulatedOffset,LayoutUnit,LayoutUnit)375 bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/)
376 {
377     if (isLineBreak())
378         return false;
379 
380     FloatPoint boxOrigin = locationIncludingFlipping();
381     boxOrigin.moveBy(accumulatedOffset);
382     FloatRect rect(boxOrigin, size());
383     if (m_truncation != cFullTruncation && visibleToHitTestRequest(request) && locationInContainer.intersects(rect)) {
384         renderer().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset)));
385         if (!result.addNodeToRectBasedTestResult(renderer().node(), request, locationInContainer, rect))
386             return true;
387     }
388     return false;
389 }
390 
paintTextWithShadows(GraphicsContext * context,const RenderObject & renderer,const Font & font,const TextRun & textRun,const AtomicString & emphasisMark,int emphasisMarkOffset,int startOffset,int endOffset,int truncationPoint,const FloatPoint & textOrigin,const FloatRect & boxRect,const ShadowList * shadowList,bool stroked,bool horizontal)391 static void paintTextWithShadows(GraphicsContext* context,
392     const RenderObject& renderer, const Font& font, const TextRun& textRun,
393     const AtomicString& emphasisMark, int emphasisMarkOffset,
394     int startOffset, int endOffset, int truncationPoint,
395     const FloatPoint& textOrigin, const FloatRect& boxRect,
396     const ShadowList* shadowList, bool stroked, bool horizontal)
397 {
398     // Text shadows are disabled when printing. http://crbug.com/258321
399     bool hasShadow = shadowList && !context->printing();
400 
401     if (hasShadow) {
402         OwnPtr<DrawLooperBuilder> drawLooperBuilder = DrawLooperBuilder::create();
403         for (size_t i = shadowList->shadows().size(); i--; ) {
404             const ShadowData& shadow = shadowList->shadows()[i];
405             float shadowX = horizontal ? shadow.x() : shadow.y();
406             float shadowY = horizontal ? shadow.y() : -shadow.x();
407             FloatSize offset(shadowX, shadowY);
408             drawLooperBuilder->addShadow(offset, shadow.blur(), shadow.color(),
409                 DrawLooperBuilder::ShadowRespectsTransforms, DrawLooperBuilder::ShadowIgnoresAlpha);
410         }
411         drawLooperBuilder->addUnmodifiedContent();
412         context->setDrawLooper(drawLooperBuilder.release());
413     }
414 
415     TextRunPaintInfo textRunPaintInfo(textRun);
416     textRunPaintInfo.bounds = boxRect;
417     if (startOffset <= endOffset) {
418         textRunPaintInfo.from = startOffset;
419         textRunPaintInfo.to = endOffset;
420         if (emphasisMark.isEmpty())
421             context->drawText(font, textRunPaintInfo, textOrigin);
422         else
423             context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset));
424     } else {
425         if (endOffset > 0) {
426             textRunPaintInfo.from = 0;
427             textRunPaintInfo.to = endOffset;
428             if (emphasisMark.isEmpty())
429                 context->drawText(font, textRunPaintInfo, textOrigin);
430             else
431                 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset));
432         }
433         if (startOffset < truncationPoint) {
434             textRunPaintInfo.from = startOffset;
435             textRunPaintInfo.to = truncationPoint;
436             if (emphasisMark.isEmpty())
437                 context->drawText(font, textRunPaintInfo, textOrigin);
438             else
439                 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset));
440         }
441     }
442 
443     if (hasShadow)
444         context->clearDrawLooper();
445 }
446 
getEmphasisMarkPosition(RenderStyle * style,TextEmphasisPosition & emphasisPosition) const447 bool InlineTextBox::getEmphasisMarkPosition(RenderStyle* style, TextEmphasisPosition& emphasisPosition) const
448 {
449     // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
450     if (style->textEmphasisMark() == TextEmphasisMarkNone)
451         return false;
452 
453     emphasisPosition = style->textEmphasisPosition();
454     if (emphasisPosition == TextEmphasisPositionUnder)
455         return true; // Ruby text is always over, so it cannot suppress emphasis marks under.
456 
457     RenderBlock* containingBlock = renderer().containingBlock();
458     if (!containingBlock->isRubyBase())
459         return true; // This text is not inside a ruby base, so it does not have ruby text over it.
460 
461     if (!containingBlock->parent()->isRubyRun())
462         return true; // Cannot get the ruby text.
463 
464     RenderRubyText* rubyText = toRenderRubyRun(containingBlock->parent())->rubyText();
465 
466     // The emphasis marks over are suppressed only if there is a ruby text box and it not empty.
467     return !rubyText || !rubyText->firstLineBox();
468 }
469 
paint(PaintInfo & paintInfo,const LayoutPoint & paintOffset,LayoutUnit,LayoutUnit)470 void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/)
471 {
472     if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(&renderer()) || renderer().style()->visibility() != VISIBLE
473         || m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len)
474         return;
475 
476     ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
477 
478     LayoutUnit logicalLeftSide = logicalLeftVisualOverflow();
479     LayoutUnit logicalRightSide = logicalRightVisualOverflow();
480     LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y());
481     LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide;
482 
483     LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
484     LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
485 
486     // When subpixel font scaling is enabled text runs are positioned at
487     // subpixel boundaries on the x-axis and thus there is no reason to
488     // snap the x value. We still round the y-axis to ensure consistent
489     // line heights.
490     LayoutPoint adjustedPaintOffset = RuntimeEnabledFeatures::subpixelFontScalingEnabled()
491         ? LayoutPoint(paintOffset.x(), paintOffset.y().round())
492         : roundedIntPoint(paintOffset);
493 
494     if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
495         return;
496 
497     bool isPrinting = textRenderer().document().printing();
498 
499     // Determine whether or not we're selected.
500     bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
501     if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
502         // When only painting the selection, don't bother to paint if there is none.
503         return;
504 
505     if (m_truncation != cNoTruncation) {
506         if (renderer().containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) {
507             // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
508             // at which we start drawing text.
509             // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
510             // |Hello|CBA| -> |...He|CBA|
511             // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
512             // farther to the right.
513             // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
514             // truncated string i.e.  |Hello|CBA| -> |...lo|CBA|
515             LayoutUnit widthOfVisibleText = toRenderText(renderer()).width(m_start, m_truncation, textPos(), isLeftToRightDirection() ? LTR : RTL, isFirstLineStyle());
516             LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
517             // FIXME: The hit testing logic also needs to take this translation into account.
518             LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0);
519             adjustedPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
520         }
521     }
522 
523     GraphicsContext* context = paintInfo.context;
524 
525     RenderObject& rendererToUse = renderer();
526     RenderStyle* styleToUse = rendererToUse.style(isFirstLineStyle());
527 
528     adjustedPaintOffset.move(0, styleToUse->isHorizontalWritingMode() ? 0 : -logicalHeight());
529 
530     FloatPoint boxOrigin = locationIncludingFlipping();
531     boxOrigin.move(adjustedPaintOffset.x().toFloat(), adjustedPaintOffset.y().toFloat());
532     FloatRect boxRect(boxOrigin, LayoutSize(logicalWidth(), logicalHeight()));
533 
534     RenderCombineText* combinedText = styleToUse->hasTextCombine() && textRenderer().isCombineText() && toRenderCombineText(textRenderer()).isCombined() ? &toRenderCombineText(textRenderer()) : 0;
535 
536     bool shouldRotate = !isHorizontal() && !combinedText;
537     if (shouldRotate)
538         context->concatCTM(rotation(boxRect, Clockwise));
539 
540     // Determine whether or not we have composition underlines to draw.
541     bool containsComposition = renderer().node() && renderer().frame()->inputMethodController().compositionNode() == renderer().node();
542     bool useCustomUnderlines = containsComposition && renderer().frame()->inputMethodController().compositionUsesCustomUnderlines();
543 
544     // Determine the text colors and selection colors.
545     Color textFillColor;
546     Color textStrokeColor;
547     Color emphasisMarkColor;
548     float textStrokeWidth = styleToUse->textStrokeWidth();
549 
550     // Text shadows are disabled when printing. http://crbug.com/258321
551     const ShadowList* textShadow = (context->printing() || paintInfo.forceBlackText()) ? 0 : styleToUse->textShadow();
552 
553     if (paintInfo.forceBlackText()) {
554         textFillColor = Color::black;
555         textStrokeColor = Color::black;
556         emphasisMarkColor = Color::black;
557     } else {
558         textFillColor = rendererToUse.resolveColor(styleToUse, CSSPropertyWebkitTextFillColor);
559 
560         bool forceBackgroundToWhite = false;
561         if (isPrinting) {
562             if (styleToUse->printColorAdjust() == PrintColorAdjustEconomy)
563                 forceBackgroundToWhite = true;
564             if (textRenderer().document().settings() && textRenderer().document().settings()->shouldPrintBackgrounds())
565                 forceBackgroundToWhite = false;
566         }
567 
568         // Make the text fill color legible against a white background
569         if (forceBackgroundToWhite)
570             textFillColor = correctedTextColor(textFillColor, Color::white);
571 
572         textStrokeColor = rendererToUse.resolveColor(styleToUse, CSSPropertyWebkitTextStrokeColor);
573 
574         // Make the text stroke color legible against a white background
575         if (forceBackgroundToWhite)
576             textStrokeColor = correctedTextColor(textStrokeColor, Color::white);
577 
578         emphasisMarkColor = rendererToUse.resolveColor(styleToUse, CSSPropertyWebkitTextEmphasisColor);
579 
580         // Make the text stroke color legible against a white background
581         if (forceBackgroundToWhite)
582             emphasisMarkColor = correctedTextColor(emphasisMarkColor, Color::white);
583     }
584 
585     bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection);
586     bool paintSelectedTextSeparately = false;
587 
588     Color selectionFillColor = textFillColor;
589     Color selectionStrokeColor = textStrokeColor;
590     Color selectionEmphasisMarkColor = emphasisMarkColor;
591     float selectionStrokeWidth = textStrokeWidth;
592     const ShadowList* selectionShadow = textShadow;
593     if (haveSelection) {
594         // Check foreground color first.
595         Color foreground = paintInfo.forceBlackText() ? Color::black : renderer().selectionForegroundColor();
596         if (foreground != selectionFillColor) {
597             if (!paintSelectedTextOnly)
598                 paintSelectedTextSeparately = true;
599             selectionFillColor = foreground;
600         }
601 
602         Color emphasisMarkForeground = paintInfo.forceBlackText() ? Color::black : renderer().selectionEmphasisMarkColor();
603         if (emphasisMarkForeground != selectionEmphasisMarkColor) {
604             if (!paintSelectedTextOnly)
605                 paintSelectedTextSeparately = true;
606             selectionEmphasisMarkColor = emphasisMarkForeground;
607         }
608 
609         if (RenderStyle* pseudoStyle = renderer().getCachedPseudoStyle(SELECTION)) {
610             // Text shadows are disabled when printing. http://crbug.com/258321
611             const ShadowList* shadow = (context->printing() || paintInfo.forceBlackText()) ? 0 : pseudoStyle->textShadow();
612             if (shadow != selectionShadow) {
613                 if (!paintSelectedTextOnly)
614                     paintSelectedTextSeparately = true;
615                 selectionShadow = shadow;
616             }
617 
618             float strokeWidth = pseudoStyle->textStrokeWidth();
619             if (strokeWidth != selectionStrokeWidth) {
620                 if (!paintSelectedTextOnly)
621                     paintSelectedTextSeparately = true;
622                 selectionStrokeWidth = strokeWidth;
623             }
624 
625             Color stroke = paintInfo.forceBlackText() ? Color::black : rendererToUse.resolveColor(pseudoStyle, CSSPropertyWebkitTextStrokeColor);
626             if (stroke != selectionStrokeColor) {
627                 if (!paintSelectedTextOnly)
628                     paintSelectedTextSeparately = true;
629                 selectionStrokeColor = stroke;
630             }
631         }
632     }
633 
634     // Set our font.
635     const Font& font = styleToUse->font();
636 
637     FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());
638 
639     if (combinedText)
640         combinedText->adjustTextOrigin(textOrigin, boxRect);
641 
642     // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
643     // and composition highlights.
644     if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
645         if (containsComposition) {
646             paintCompositionBackgrounds(context, boxOrigin, styleToUse, font, useCustomUnderlines);
647         }
648 
649         paintDocumentMarkers(context, boxOrigin, styleToUse, font, true);
650 
651         if (haveSelection && !useCustomUnderlines)
652             paintSelection(context, boxOrigin, styleToUse, font, selectionFillColor);
653     }
654 
655     // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
656     int length = m_len;
657     int maximumLength;
658     StringView string;
659     if (!combinedText) {
660         string = textRenderer().text().createView();
661         if (static_cast<unsigned>(length) != string.length() || m_start)
662             string.narrow(m_start, length);
663         maximumLength = textRenderer().textLength() - m_start;
664     } else {
665         combinedText->getStringToRender(m_start, string, length);
666         maximumLength = length;
667     }
668 
669     StringBuilder charactersWithHyphen;
670     TextRun textRun = constructTextRun(styleToUse, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0);
671     if (hasHyphen())
672         length = textRun.length();
673 
674     int sPos = 0;
675     int ePos = 0;
676     if (paintSelectedTextOnly || paintSelectedTextSeparately)
677         selectionStartEnd(sPos, ePos);
678 
679     if (m_truncation != cNoTruncation) {
680         sPos = min<int>(sPos, m_truncation);
681         ePos = min<int>(ePos, m_truncation);
682         length = m_truncation;
683     }
684 
685     int emphasisMarkOffset = 0;
686     TextEmphasisPosition emphasisMarkPosition;
687     bool hasTextEmphasis = getEmphasisMarkPosition(styleToUse, emphasisMarkPosition);
688     const AtomicString& emphasisMark = hasTextEmphasis ? styleToUse->textEmphasisMarkString() : nullAtom;
689     if (!emphasisMark.isEmpty())
690         emphasisMarkOffset = emphasisMarkPosition == TextEmphasisPositionOver ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
691 
692     if (!paintSelectedTextOnly) {
693         // For stroked painting, we have to change the text drawing mode.  It's probably dangerous to leave that mutated as a side
694         // effect, so only when we know we're stroking, do a save/restore.
695         GraphicsContextStateSaver stateSaver(*context, textStrokeWidth > 0);
696 
697         updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth);
698         if (!paintSelectedTextSeparately || ePos <= sPos) {
699             // FIXME: Truncate right-to-left text correctly.
700             paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
701         } else {
702             paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
703         }
704 
705         if (!emphasisMark.isEmpty()) {
706             updateGraphicsContext(context, emphasisMarkColor, textStrokeColor, textStrokeWidth);
707 
708             DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
709             TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
710             FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
711             if (combinedText)
712                 context->concatCTM(rotation(boxRect, Clockwise));
713 
714             int startOffset = 0;
715             int endOffset = length;
716             int paintRunLength = length;
717             if (combinedText) {
718                 startOffset = 0;
719                 endOffset = objectReplacementCharacterTextRun.length();
720                 paintRunLength = endOffset;
721             } else if (paintSelectedTextSeparately && ePos > sPos) {
722                 startOffset = ePos;
723                 endOffset = sPos;
724             }
725             // FIXME: Truncate right-to-left text correctly.
726             paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, startOffset, endOffset, paintRunLength, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
727 
728             if (combinedText)
729                 context->concatCTM(rotation(boxRect, Counterclockwise));
730         }
731     }
732 
733     if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) {
734         // paint only the text that is selected
735         GraphicsContextStateSaver stateSaver(*context, selectionStrokeWidth > 0);
736 
737         updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth);
738         paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
739         if (!emphasisMark.isEmpty()) {
740             updateGraphicsContext(context, selectionEmphasisMarkColor, textStrokeColor, textStrokeWidth);
741 
742             DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
743             TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
744             FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
745             if (combinedText)
746                 context->concatCTM(rotation(boxRect, Clockwise));
747 
748             int startOffset = combinedText ? 0 : sPos;
749             int endOffset = combinedText ? objectReplacementCharacterTextRun.length() : ePos;
750             int paintRunLength = combinedText ? endOffset : length;
751             paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, startOffset, endOffset, paintRunLength, emphasisMarkTextOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
752 
753             if (combinedText)
754                 context->concatCTM(rotation(boxRect, Counterclockwise));
755         }
756     }
757 
758     // Paint decorations
759     TextDecoration textDecorations = styleToUse->textDecorationsInEffect();
760     if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) {
761         updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth);
762         if (combinedText)
763             context->concatCTM(rotation(boxRect, Clockwise));
764         paintDecoration(context, boxOrigin, textDecorations, textShadow);
765         if (combinedText)
766             context->concatCTM(rotation(boxRect, Counterclockwise));
767     }
768 
769     if (paintInfo.phase == PaintPhaseForeground) {
770         paintDocumentMarkers(context, boxOrigin, styleToUse, font, false);
771 
772         // Paint custom underlines for compositions.
773         if (useCustomUnderlines) {
774             const Vector<CompositionUnderline>& underlines = renderer().frame()->inputMethodController().customCompositionUnderlines();
775             CompositionUnderlineRangeFilter filter(underlines, start(), end());
776             for (CompositionUnderlineRangeFilter::ConstIterator it = filter.begin(); it != filter.end(); ++it) {
777                 if (it->color == Color::transparent)
778                     continue;
779                 paintCompositionUnderline(context, boxOrigin, *it);
780             }
781         }
782     }
783 
784     if (shouldRotate)
785         context->concatCTM(rotation(boxRect, Counterclockwise));
786 }
787 
selectionStartEnd(int & sPos,int & ePos)788 void InlineTextBox::selectionStartEnd(int& sPos, int& ePos)
789 {
790     int startPos, endPos;
791     if (renderer().selectionState() == RenderObject::SelectionInside) {
792         startPos = 0;
793         endPos = textRenderer().textLength();
794     } else {
795         textRenderer().selectionStartEnd(startPos, endPos);
796         if (renderer().selectionState() == RenderObject::SelectionStart)
797             endPos = textRenderer().textLength();
798         else if (renderer().selectionState() == RenderObject::SelectionEnd)
799             startPos = 0;
800     }
801 
802     sPos = max(startPos - m_start, 0);
803     ePos = min(endPos - m_start, (int)m_len);
804 }
805 
alignSelectionRectToDevicePixels(FloatRect & rect)806 void alignSelectionRectToDevicePixels(FloatRect& rect)
807 {
808     float maxX = floorf(rect.maxX());
809     rect.setX(floorf(rect.x()));
810     rect.setWidth(roundf(maxX - rect.x()));
811 }
812 
paintSelection(GraphicsContext * context,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font,Color textColor)813 void InlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, Color textColor)
814 {
815     if (context->paintingDisabled())
816         return;
817 
818     // See if we have a selection to paint at all.
819     int sPos, ePos;
820     selectionStartEnd(sPos, ePos);
821     if (sPos >= ePos)
822         return;
823 
824     Color c = renderer().selectionBackgroundColor();
825     if (!c.alpha())
826         return;
827 
828     // If the text color ends up being the same as the selection background, invert the selection
829     // background.
830     if (textColor == c)
831         c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
832 
833     GraphicsContextStateSaver stateSaver(*context);
834     updateGraphicsContext(context, c, c, 0); // Don't draw text at all!
835 
836     // If the text is truncated, let the thing being painted in the truncation
837     // draw its own highlight.
838     int length = m_truncation != cNoTruncation ? m_truncation : m_len;
839     StringView string = textRenderer().text().createView();
840 
841     if (string.length() != static_cast<unsigned>(length) || m_start)
842         string.narrow(m_start, length);
843 
844     StringBuilder charactersWithHyphen;
845     bool respectHyphen = ePos == length && hasHyphen();
846     TextRun textRun = constructTextRun(style, font, string, textRenderer().textLength() - m_start, respectHyphen ? &charactersWithHyphen : 0);
847     if (respectHyphen)
848         ePos = textRun.length();
849 
850     LayoutUnit selectionBottom = root().selectionBottom();
851     LayoutUnit selectionTop = root().selectionTopAdjustedForPrecedingBlock();
852 
853     int deltaY = roundToInt(renderer().style()->isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop);
854     int selHeight = max(0, roundToInt(selectionBottom - selectionTop));
855 
856     FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
857     FloatRect clipRect(localOrigin, FloatSize(m_logicalWidth, selHeight));
858     alignSelectionRectToDevicePixels(clipRect);
859 
860     context->clip(clipRect);
861 
862     context->drawHighlightForText(font, textRun, localOrigin, selHeight, c, sPos, ePos);
863 }
864 
underlinePaintStart(const CompositionUnderline & underline)865 unsigned InlineTextBox::underlinePaintStart(const CompositionUnderline& underline)
866 {
867     return std::max(static_cast<unsigned>(m_start), underline.startOffset);
868 }
869 
underlinePaintEnd(const CompositionUnderline & underline)870 unsigned InlineTextBox::underlinePaintEnd(const CompositionUnderline& underline)
871 {
872     unsigned paintEnd = std::min(end() + 1, underline.endOffset); // end() points at the last char, not past it.
873     if (m_truncation != cNoTruncation)
874         paintEnd = std::min(paintEnd, static_cast<unsigned>(m_start + m_truncation));
875     return paintEnd;
876 }
877 
paintSingleCompositionBackgroundRun(GraphicsContext * context,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font,Color backgroundColor,int startPos,int endPos)878 void InlineTextBox::paintSingleCompositionBackgroundRun(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, Color backgroundColor, int startPos, int endPos)
879 {
880     int sPos = std::max(startPos - m_start, 0);
881     int ePos = std::min(endPos - m_start, static_cast<int>(m_len));
882     if (sPos >= ePos)
883         return;
884 
885     GraphicsContextStateSaver stateSaver(*context);
886 
887     updateGraphicsContext(context, backgroundColor, backgroundColor, 0); // Don't draw text at all!
888 
889     int deltaY = renderer().style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
890     int selHeight = selectionHeight();
891     FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
892     context->drawHighlightForText(font, constructTextRun(style, font), localOrigin, selHeight, backgroundColor, sPos, ePos);
893 }
894 
textDecorationStyleToStrokeStyle(TextDecorationStyle decorationStyle)895 static StrokeStyle textDecorationStyleToStrokeStyle(TextDecorationStyle decorationStyle)
896 {
897     StrokeStyle strokeStyle = SolidStroke;
898     switch (decorationStyle) {
899     case TextDecorationStyleSolid:
900         strokeStyle = SolidStroke;
901         break;
902     case TextDecorationStyleDouble:
903         strokeStyle = DoubleStroke;
904         break;
905     case TextDecorationStyleDotted:
906         strokeStyle = DottedStroke;
907         break;
908     case TextDecorationStyleDashed:
909         strokeStyle = DashedStroke;
910         break;
911     case TextDecorationStyleWavy:
912         strokeStyle = WavyStroke;
913         break;
914     }
915 
916     return strokeStyle;
917 }
918 
computeUnderlineOffset(const TextUnderlinePosition underlinePosition,const FontMetrics & fontMetrics,const InlineTextBox * inlineTextBox,const float textDecorationThickness)919 static int computeUnderlineOffset(const TextUnderlinePosition underlinePosition, const FontMetrics& fontMetrics, const InlineTextBox* inlineTextBox, const float textDecorationThickness)
920 {
921     // Compute the gap between the font and the underline. Use at least one
922     // pixel gap, if underline is thick then use a bigger gap.
923     int gap = 0;
924 
925     // Underline position of zero means draw underline on Baseline Position,
926     // in Blink we need at least 1-pixel gap to adding following check.
927     // Positive underline Position means underline should be drawn above baselin e
928     // and negative value means drawing below baseline, negating the value as in Blink
929     // downward Y-increases.
930 
931     if (fontMetrics.underlinePosition())
932         gap = -fontMetrics.underlinePosition();
933     else
934         gap = std::max<int>(1, ceilf(textDecorationThickness / 2.f));
935 
936     // FIXME: We support only horizontal text for now.
937     switch (underlinePosition) {
938     case TextUnderlinePositionAuto:
939         return fontMetrics.ascent() + gap; // Position underline near the alphabetic baseline.
940     case TextUnderlinePositionUnder: {
941         // Position underline relative to the under edge of the lowest element's content box.
942         const float offset = inlineTextBox->root().maxLogicalTop() - inlineTextBox->logicalTop();
943         if (offset > 0)
944             return inlineTextBox->logicalHeight() + gap + offset;
945         return inlineTextBox->logicalHeight() + gap;
946     }
947     }
948 
949     ASSERT_NOT_REACHED();
950     return fontMetrics.ascent() + gap;
951 }
952 
adjustStepToDecorationLength(float & step,float & controlPointDistance,float length)953 static void adjustStepToDecorationLength(float& step, float& controlPointDistance, float length)
954 {
955     ASSERT(step > 0);
956 
957     if (length <= 0)
958         return;
959 
960     unsigned stepCount = static_cast<unsigned>(length / step);
961 
962     // Each Bezier curve starts at the same pixel that the previous one
963     // ended. We need to subtract (stepCount - 1) pixels when calculating the
964     // length covered to account for that.
965     float uncoveredLength = length - (stepCount * step - (stepCount - 1));
966     float adjustment = uncoveredLength / stepCount;
967     step += adjustment;
968     controlPointDistance += adjustment;
969 }
970 
971 /*
972  * Draw one cubic Bezier curve and repeat the same pattern long the the decoration's axis.
973  * The start point (p1), controlPoint1, controlPoint2 and end point (p2) of the Bezier curve
974  * form a diamond shape:
975  *
976  *                              step
977  *                         |-----------|
978  *
979  *                   controlPoint1
980  *                         +
981  *
982  *
983  *                  . .
984  *                .     .
985  *              .         .
986  * (x1, y1) p1 +           .            + p2 (x2, y2) - <--- Decoration's axis
987  *                          .         .               |
988  *                            .     .                 |
989  *                              . .                   | controlPointDistance
990  *                                                    |
991  *                                                    |
992  *                         +                          -
993  *                   controlPoint2
994  *
995  *             |-----------|
996  *                 step
997  */
strokeWavyTextDecoration(GraphicsContext * context,FloatPoint p1,FloatPoint p2,float strokeThickness)998 static void strokeWavyTextDecoration(GraphicsContext* context, FloatPoint p1, FloatPoint p2, float strokeThickness)
999 {
1000     context->adjustLineToPixelBoundaries(p1, p2, strokeThickness, context->strokeStyle());
1001 
1002     Path path;
1003     path.moveTo(p1);
1004 
1005     // Distance between decoration's axis and Bezier curve's control points.
1006     // The height of the curve is based on this distance. Use a minimum of 6 pixels distance since
1007     // the actual curve passes approximately at half of that distance, that is 3 pixels.
1008     // The minimum height of the curve is also approximately 3 pixels. Increases the curve's height
1009     // as strockThickness increases to make the curve looks better.
1010     float controlPointDistance = 3 * max<float>(2, strokeThickness);
1011 
1012     // Increment used to form the diamond shape between start point (p1), control
1013     // points and end point (p2) along the axis of the decoration. Makes the
1014     // curve wider as strockThickness increases to make the curve looks better.
1015     float step = 2 * max<float>(2, strokeThickness);
1016 
1017     bool isVerticalLine = (p1.x() == p2.x());
1018 
1019     if (isVerticalLine) {
1020         ASSERT(p1.x() == p2.x());
1021 
1022         float xAxis = p1.x();
1023         float y1;
1024         float y2;
1025 
1026         if (p1.y() < p2.y()) {
1027             y1 = p1.y();
1028             y2 = p2.y();
1029         } else {
1030             y1 = p2.y();
1031             y2 = p1.y();
1032         }
1033 
1034         adjustStepToDecorationLength(step, controlPointDistance, y2 - y1);
1035         FloatPoint controlPoint1(xAxis + controlPointDistance, 0);
1036         FloatPoint controlPoint2(xAxis - controlPointDistance, 0);
1037 
1038         for (float y = y1; y + 2 * step <= y2;) {
1039             controlPoint1.setY(y + step);
1040             controlPoint2.setY(y + step);
1041             y += 2 * step;
1042             path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y));
1043         }
1044     } else {
1045         ASSERT(p1.y() == p2.y());
1046 
1047         float yAxis = p1.y();
1048         float x1;
1049         float x2;
1050 
1051         if (p1.x() < p2.x()) {
1052             x1 = p1.x();
1053             x2 = p2.x();
1054         } else {
1055             x1 = p2.x();
1056             x2 = p1.x();
1057         }
1058 
1059         adjustStepToDecorationLength(step, controlPointDistance, x2 - x1);
1060         FloatPoint controlPoint1(0, yAxis + controlPointDistance);
1061         FloatPoint controlPoint2(0, yAxis - controlPointDistance);
1062 
1063         for (float x = x1; x + 2 * step <= x2;) {
1064             controlPoint1.setX(x + step);
1065             controlPoint2.setX(x + step);
1066             x += 2 * step;
1067             path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis));
1068         }
1069     }
1070 
1071     context->setShouldAntialias(true);
1072     context->strokePath(path);
1073 }
1074 
shouldSetDecorationAntialias(TextDecorationStyle decorationStyle)1075 static bool shouldSetDecorationAntialias(TextDecorationStyle decorationStyle)
1076 {
1077     return decorationStyle == TextDecorationStyleDotted || decorationStyle == TextDecorationStyleDashed;
1078 }
1079 
shouldSetDecorationAntialias(TextDecorationStyle underline,TextDecorationStyle overline,TextDecorationStyle linethrough)1080 static bool shouldSetDecorationAntialias(TextDecorationStyle underline, TextDecorationStyle overline, TextDecorationStyle linethrough)
1081 {
1082     return shouldSetDecorationAntialias(underline) || shouldSetDecorationAntialias(overline) || shouldSetDecorationAntialias(linethrough);
1083 }
1084 
paintAppliedDecoration(GraphicsContext * context,FloatPoint start,float width,float doubleOffset,int wavyOffsetFactor,RenderObject::AppliedTextDecoration decoration,float thickness,bool antialiasDecoration,bool isPrinting)1085 static void paintAppliedDecoration(GraphicsContext* context, FloatPoint start, float width, float doubleOffset, int wavyOffsetFactor,
1086     RenderObject::AppliedTextDecoration decoration, float thickness, bool antialiasDecoration, bool isPrinting)
1087 {
1088     context->setStrokeStyle(textDecorationStyleToStrokeStyle(decoration.style));
1089     context->setStrokeColor(decoration.color);
1090 
1091     switch (decoration.style) {
1092     case TextDecorationStyleWavy:
1093         strokeWavyTextDecoration(context, start + FloatPoint(0, doubleOffset * wavyOffsetFactor), start + FloatPoint(width, doubleOffset * wavyOffsetFactor), thickness);
1094         break;
1095     case TextDecorationStyleDotted:
1096     case TextDecorationStyleDashed:
1097         context->setShouldAntialias(antialiasDecoration);
1098         // Fall through
1099     default:
1100         context->drawLineForText(start, width, isPrinting);
1101 
1102         if (decoration.style == TextDecorationStyleDouble)
1103             context->drawLineForText(start + FloatPoint(0, doubleOffset), width, isPrinting);
1104     }
1105 }
1106 
paintDecoration(GraphicsContext * context,const FloatPoint & boxOrigin,TextDecoration deco,const ShadowList * shadowList)1107 void InlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& boxOrigin, TextDecoration deco, const ShadowList* shadowList)
1108 {
1109     GraphicsContextStateSaver stateSaver(*context);
1110 
1111     if (m_truncation == cFullTruncation)
1112         return;
1113 
1114     FloatPoint localOrigin = boxOrigin;
1115 
1116     float width = m_logicalWidth;
1117     if (m_truncation != cNoTruncation) {
1118         width = toRenderText(renderer()).width(m_start, m_truncation, textPos(), isLeftToRightDirection() ? LTR : RTL, isFirstLineStyle());
1119         if (!isLeftToRightDirection())
1120             localOrigin.move(m_logicalWidth - width, 0);
1121     }
1122 
1123     // Get the text decoration colors.
1124     RenderObject::AppliedTextDecoration underline, overline, linethrough;
1125 
1126     renderer().getTextDecorations(deco, underline, overline, linethrough, true);
1127     if (isFirstLineStyle())
1128         renderer().getTextDecorations(deco, underline, overline, linethrough, true, true);
1129 
1130     // Use a special function for underlines to get the positioning exactly right.
1131     bool isPrinting = textRenderer().document().printing();
1132 
1133     bool linesAreOpaque = !isPrinting && (!(deco & TextDecorationUnderline) || underline.color.alpha() == 255) && (!(deco & TextDecorationOverline) || overline.color.alpha() == 255) && (!(deco & TextDecorationLineThrough) || linethrough.color.alpha() == 255);
1134 
1135     RenderStyle* styleToUse = renderer().style(isFirstLineStyle());
1136     int baseline = styleToUse->fontMetrics().ascent();
1137 
1138     size_t shadowCount = shadowList ? shadowList->shadows().size() : 0;
1139     // Set the thick of the line to be 10% (or something else ?)of the computed font size and not less than 1px.
1140     // Using computedFontSize should take care of zoom as well.
1141 
1142     // Update Underline thickness, in case we have Faulty Font Metrics calculating underline thickness by old method.
1143     float textDecorationThickness = styleToUse->fontMetrics().underlineThickness();
1144     int fontHeightInt  = (int)(styleToUse->fontMetrics().floatHeight() + 0.5);
1145     if ((textDecorationThickness == 0.f) || (textDecorationThickness >= (fontHeightInt >> 1)))
1146         textDecorationThickness = std::max(1.f, styleToUse->computedFontSize() / 10.f);
1147 
1148     context->setStrokeThickness(textDecorationThickness);
1149 
1150     bool antialiasDecoration = shouldSetDecorationAntialias(overline.style, underline.style, linethrough.style)
1151         && RenderBoxModelObject::shouldAntialiasLines(context);
1152 
1153     float extraOffset = 0;
1154     if (!linesAreOpaque && shadowCount > 1) {
1155         FloatRect clipRect(localOrigin, FloatSize(width, baseline + 2));
1156         for (size_t i = shadowCount; i--; ) {
1157             const ShadowData& s = shadowList->shadows()[i];
1158             FloatRect shadowRect(localOrigin, FloatSize(width, baseline + 2));
1159             shadowRect.inflate(s.blur());
1160             float shadowX = isHorizontal() ? s.x() : s.y();
1161             float shadowY = isHorizontal() ? s.y() : -s.x();
1162             shadowRect.move(shadowX, shadowY);
1163             clipRect.unite(shadowRect);
1164             extraOffset = max(extraOffset, max(0.0f, shadowY) + s.blur());
1165         }
1166         context->clip(clipRect);
1167         extraOffset += baseline + 2;
1168         localOrigin.move(0, extraOffset);
1169     }
1170 
1171     for (size_t i = max(static_cast<size_t>(1), shadowCount); i--; ) {
1172         // Even if we have no shadows, we still want to run the code below this once.
1173         if (i < shadowCount) {
1174             if (!i) {
1175                 // The last set of lines paints normally inside the clip.
1176                 localOrigin.move(0, -extraOffset);
1177                 extraOffset = 0;
1178             }
1179             const ShadowData& shadow = shadowList->shadows()[i];
1180             float shadowX = isHorizontal() ? shadow.x() : shadow.y();
1181             float shadowY = isHorizontal() ? shadow.y() : -shadow.x();
1182             context->setShadow(FloatSize(shadowX, shadowY - extraOffset), shadow.blur(), shadow.color());
1183         }
1184 
1185         // Offset between lines - always non-zero, so lines never cross each other.
1186         float doubleOffset = textDecorationThickness + 1.f;
1187 
1188         if (deco & TextDecorationUnderline) {
1189             const int underlineOffset = computeUnderlineOffset(styleToUse->textUnderlinePosition(), styleToUse->fontMetrics(), this, textDecorationThickness);
1190             paintAppliedDecoration(context, localOrigin + FloatPoint(0, underlineOffset), width, doubleOffset, 1, underline, textDecorationThickness, antialiasDecoration, isPrinting);
1191         }
1192         if (deco & TextDecorationOverline) {
1193             paintAppliedDecoration(context, localOrigin, width, -doubleOffset, 1, overline, textDecorationThickness, antialiasDecoration, isPrinting);
1194         }
1195         if (deco & TextDecorationLineThrough) {
1196             const float lineThroughOffset = 2 * baseline / 3;
1197             paintAppliedDecoration(context, localOrigin + FloatPoint(0, lineThroughOffset), width, doubleOffset, 0, linethrough, textDecorationThickness, antialiasDecoration, isPrinting);
1198         }
1199     }
1200 }
1201 
lineStyleForMarkerType(DocumentMarker::MarkerType markerType)1202 static GraphicsContext::DocumentMarkerLineStyle lineStyleForMarkerType(DocumentMarker::MarkerType markerType)
1203 {
1204     switch (markerType) {
1205     case DocumentMarker::Spelling:
1206         return GraphicsContext::DocumentMarkerSpellingLineStyle;
1207     case DocumentMarker::Grammar:
1208         return GraphicsContext::DocumentMarkerGrammarLineStyle;
1209     default:
1210         ASSERT_NOT_REACHED();
1211         return GraphicsContext::DocumentMarkerSpellingLineStyle;
1212     }
1213 }
1214 
paintDocumentMarker(GraphicsContext * pt,const FloatPoint & boxOrigin,DocumentMarker * marker,RenderStyle * style,const Font & font,bool grammar)1215 void InlineTextBox::paintDocumentMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, RenderStyle* style, const Font& font, bool grammar)
1216 {
1217     // Never print spelling/grammar markers (5327887)
1218     if (textRenderer().document().printing())
1219         return;
1220 
1221     if (m_truncation == cFullTruncation)
1222         return;
1223 
1224     float start = 0; // start of line to draw, relative to tx
1225     float width = m_logicalWidth; // how much line to draw
1226 
1227     // Determine whether we need to measure text
1228     bool markerSpansWholeBox = true;
1229     if (m_start <= (int)marker->startOffset())
1230         markerSpansWholeBox = false;
1231     if ((end() + 1) != marker->endOffset()) // end points at the last char, not past it
1232         markerSpansWholeBox = false;
1233     if (m_truncation != cNoTruncation)
1234         markerSpansWholeBox = false;
1235 
1236     if (!markerSpansWholeBox || grammar) {
1237         int startPosition = max<int>(marker->startOffset() - m_start, 0);
1238         int endPosition = min<int>(marker->endOffset() - m_start, m_len);
1239 
1240         if (m_truncation != cNoTruncation)
1241             endPosition = min<int>(endPosition, m_truncation);
1242 
1243         // Calculate start & width
1244         int deltaY = renderer().style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1245         int selHeight = selectionHeight();
1246         FloatPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY);
1247         TextRun run = constructTextRun(style, font);
1248 
1249         // FIXME: Convert the document markers to float rects.
1250         IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, selHeight, startPosition, endPosition));
1251         start = markerRect.x() - startPoint.x();
1252         width = markerRect.width();
1253 
1254         // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to
1255         // display a toolTip. We don't do this for misspelling markers.
1256         if (grammar) {
1257             markerRect.move(-boxOrigin.x(), -boxOrigin.y());
1258             markerRect = renderer().localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1259             toRenderedDocumentMarker(marker)->setRenderedRect(markerRect);
1260         }
1261     }
1262 
1263     // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to
1264     // make sure to fit within those bounds.  This means the top pixel(s) of the underline will overlap the
1265     // bottom pixel(s) of the glyphs in smaller font sizes.  The alternatives are to increase the line spacing (bad!!)
1266     // or decrease the underline thickness.  The overlap is actually the most useful, and matches what AppKit does.
1267     // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so
1268     // we pin to two pixels under the baseline.
1269     int lineThickness = misspellingLineThickness;
1270     int baseline = renderer().style(isFirstLineStyle())->fontMetrics().ascent();
1271     int descent = logicalHeight() - baseline;
1272     int underlineOffset;
1273     if (descent <= (2 + lineThickness)) {
1274         // Place the underline at the very bottom of the text in small/medium fonts.
1275         underlineOffset = logicalHeight() - lineThickness;
1276     } else {
1277         // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
1278         underlineOffset = baseline + 2;
1279     }
1280     pt->drawLineForDocumentMarker(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, lineStyleForMarkerType(marker->type()));
1281 }
1282 
paintTextMatchMarker(GraphicsContext * pt,const FloatPoint & boxOrigin,DocumentMarker * marker,RenderStyle * style,const Font & font)1283 void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, RenderStyle* style, const Font& font)
1284 {
1285     // Use same y positioning and height as for selection, so that when the selection and this highlight are on
1286     // the same word there are no pieces sticking out.
1287     int deltaY = renderer().style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1288     int selHeight = selectionHeight();
1289 
1290     int sPos = max(marker->startOffset() - m_start, (unsigned)0);
1291     int ePos = min(marker->endOffset() - m_start, (unsigned)m_len);
1292     TextRun run = constructTextRun(style, font);
1293 
1294     // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates.
1295     IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, IntPoint(x(), selectionTop()), selHeight, sPos, ePos));
1296     markerRect = renderer().localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1297     toRenderedDocumentMarker(marker)->setRenderedRect(markerRect);
1298 
1299     // Optionally highlight the text
1300     if (renderer().frame()->editor().markedTextMatchesAreHighlighted()) {
1301         Color color = marker->activeMatch() ?
1302             RenderTheme::theme().platformActiveTextSearchHighlightColor() :
1303             RenderTheme::theme().platformInactiveTextSearchHighlightColor();
1304         GraphicsContextStateSaver stateSaver(*pt);
1305         updateGraphicsContext(pt, color, color, 0); // Don't draw text at all!
1306         pt->clip(FloatRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selHeight));
1307         pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, sPos, ePos);
1308     }
1309 }
1310 
paintCompositionBackgrounds(GraphicsContext * pt,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font,bool useCustomUnderlines)1311 void InlineTextBox::paintCompositionBackgrounds(GraphicsContext* pt, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, bool useCustomUnderlines)
1312 {
1313     if (useCustomUnderlines) {
1314         // Paint custom background highlights for compositions.
1315         const Vector<CompositionUnderline>& underlines = renderer().frame()->inputMethodController().customCompositionUnderlines();
1316         CompositionUnderlineRangeFilter filter(underlines, start(), end());
1317         for (CompositionUnderlineRangeFilter::ConstIterator it = filter.begin(); it != filter.end(); ++it) {
1318             if (it->backgroundColor == Color::transparent)
1319                 continue;
1320             paintSingleCompositionBackgroundRun(pt, boxOrigin, style, font, it->backgroundColor, underlinePaintStart(*it), underlinePaintEnd(*it));
1321         }
1322 
1323     } else {
1324         paintSingleCompositionBackgroundRun(pt, boxOrigin, style, font, RenderTheme::theme().platformDefaultCompositionBackgroundColor(),
1325             renderer().frame()->inputMethodController().compositionStart(),
1326             renderer().frame()->inputMethodController().compositionEnd());
1327     }
1328 }
1329 
paintDocumentMarkers(GraphicsContext * pt,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font,bool background)1330 void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, bool background)
1331 {
1332     if (!renderer().node())
1333         return;
1334 
1335     WillBeHeapVector<DocumentMarker*> markers = renderer().document().markers().markersFor(renderer().node());
1336     WillBeHeapVector<DocumentMarker*>::const_iterator markerIt = markers.begin();
1337 
1338     // Give any document markers that touch this run a chance to draw before the text has been drawn.
1339     // Note end() points at the last char, not one past it like endOffset and ranges do.
1340     for ( ; markerIt != markers.end(); ++markerIt) {
1341         DocumentMarker* marker = *markerIt;
1342 
1343         // Paint either the background markers or the foreground markers, but not both
1344         switch (marker->type()) {
1345             case DocumentMarker::Grammar:
1346             case DocumentMarker::Spelling:
1347                 if (background)
1348                     continue;
1349                 break;
1350             case DocumentMarker::TextMatch:
1351                 if (!background)
1352                     continue;
1353                 break;
1354             default:
1355                 continue;
1356         }
1357 
1358         if (marker->endOffset() <= start())
1359             // marker is completely before this run.  This might be a marker that sits before the
1360             // first run we draw, or markers that were within runs we skipped due to truncation.
1361             continue;
1362 
1363         if (marker->startOffset() > end())
1364             // marker is completely after this run, bail.  A later run will paint it.
1365             break;
1366 
1367         // marker intersects this run.  Paint it.
1368         switch (marker->type()) {
1369             case DocumentMarker::Spelling:
1370                 paintDocumentMarker(pt, boxOrigin, marker, style, font, false);
1371                 break;
1372             case DocumentMarker::Grammar:
1373                 paintDocumentMarker(pt, boxOrigin, marker, style, font, true);
1374                 break;
1375             case DocumentMarker::TextMatch:
1376                 paintTextMatchMarker(pt, boxOrigin, marker, style, font);
1377                 break;
1378             default:
1379                 ASSERT_NOT_REACHED();
1380         }
1381 
1382     }
1383 }
1384 
paintCompositionUnderline(GraphicsContext * ctx,const FloatPoint & boxOrigin,const CompositionUnderline & underline)1385 void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const FloatPoint& boxOrigin, const CompositionUnderline& underline)
1386 {
1387     if (m_truncation == cFullTruncation)
1388         return;
1389 
1390     unsigned paintStart = underlinePaintStart(underline);
1391     unsigned paintEnd = underlinePaintEnd(underline);
1392 
1393     // start of line to draw, relative to paintOffset.
1394     float start = paintStart == static_cast<unsigned>(m_start) ? 0 :
1395         toRenderText(renderer()).width(m_start, paintStart - m_start, textPos(), isLeftToRightDirection() ? LTR : RTL, isFirstLineStyle());
1396     // how much line to draw
1397     float width = (paintStart == static_cast<unsigned>(m_start) && paintEnd == static_cast<unsigned>(end()) + 1) ? m_logicalWidth :
1398         toRenderText(renderer()).width(paintStart, paintEnd - paintStart, textPos() + start, isLeftToRightDirection() ? LTR : RTL, isFirstLineStyle());
1399 
1400     // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
1401     // All other marked text underlines are 1px thick.
1402     // If there's not enough space the underline will touch or overlap characters.
1403     int lineThickness = 1;
1404     int baseline = renderer().style(isFirstLineStyle())->fontMetrics().ascent();
1405     if (underline.thick && logicalHeight() - baseline >= 2)
1406         lineThickness = 2;
1407 
1408     // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
1409     // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
1410     start += 1;
1411     width -= 2;
1412 
1413     ctx->setStrokeColor(underline.color);
1414     ctx->setStrokeThickness(lineThickness);
1415     ctx->drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, textRenderer().document().printing());
1416 }
1417 
caretMinOffset() const1418 int InlineTextBox::caretMinOffset() const
1419 {
1420     return m_start;
1421 }
1422 
caretMaxOffset() const1423 int InlineTextBox::caretMaxOffset() const
1424 {
1425     return m_start + m_len;
1426 }
1427 
textPos() const1428 float InlineTextBox::textPos() const
1429 {
1430     // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset
1431     // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width.
1432     if (logicalLeft() == 0)
1433         return 0;
1434     return logicalLeft() - root().logicalLeft();
1435 }
1436 
offsetForPosition(float lineOffset,bool includePartialGlyphs) const1437 int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs) const
1438 {
1439     if (isLineBreak())
1440         return 0;
1441 
1442     if (lineOffset - logicalLeft() > logicalWidth())
1443         return isLeftToRightDirection() ? len() : 0;
1444     if (lineOffset - logicalLeft() < 0)
1445         return isLeftToRightDirection() ? 0 : len();
1446 
1447     FontCachePurgePreventer fontCachePurgePreventer;
1448 
1449     RenderText& text = toRenderText(renderer());
1450     RenderStyle* style = text.style(isFirstLineStyle());
1451     const Font& font = style->font();
1452     return font.offsetForPosition(constructTextRun(style, font), lineOffset - logicalLeft(), includePartialGlyphs);
1453 }
1454 
positionForOffset(int offset) const1455 float InlineTextBox::positionForOffset(int offset) const
1456 {
1457     ASSERT(offset >= m_start);
1458     ASSERT(offset <= m_start + m_len);
1459 
1460     if (isLineBreak())
1461         return logicalLeft();
1462 
1463     FontCachePurgePreventer fontCachePurgePreventer;
1464 
1465     RenderText& text = toRenderText(renderer());
1466     RenderStyle* styleToUse = text.style(isFirstLineStyle());
1467     ASSERT(styleToUse);
1468     const Font& font = styleToUse->font();
1469     int from = !isLeftToRightDirection() ? offset - m_start : 0;
1470     int to = !isLeftToRightDirection() ? m_len : offset - m_start;
1471     // FIXME: Do we need to add rightBearing here?
1472     return font.selectionRectForText(constructTextRun(styleToUse, font), IntPoint(logicalLeft(), 0), 0, from, to).maxX();
1473 }
1474 
containsCaretOffset(int offset) const1475 bool InlineTextBox::containsCaretOffset(int offset) const
1476 {
1477     // Offsets before the box are never "in".
1478     if (offset < m_start)
1479         return false;
1480 
1481     int pastEnd = m_start + m_len;
1482 
1483     // Offsets inside the box (not at either edge) are always "in".
1484     if (offset < pastEnd)
1485         return true;
1486 
1487     // Offsets outside the box are always "out".
1488     if (offset > pastEnd)
1489         return false;
1490 
1491     // Offsets at the end are "out" for line breaks (they are on the next line).
1492     if (isLineBreak())
1493         return false;
1494 
1495     // Offsets at the end are "in" for normal boxes (but the caller has to check affinity).
1496     return true;
1497 }
1498 
characterWidths(Vector<float> & widths) const1499 void InlineTextBox::characterWidths(Vector<float>& widths) const
1500 {
1501     FontCachePurgePreventer fontCachePurgePreventer;
1502 
1503     RenderStyle* styleToUse = textRenderer().style(isFirstLineStyle());
1504     const Font& font = styleToUse->font();
1505 
1506     TextRun textRun = constructTextRun(styleToUse, font);
1507 
1508     GlyphBuffer glyphBuffer;
1509     WidthIterator it(&font, textRun);
1510     float lastWidth = 0;
1511     widths.resize(m_len);
1512     for (unsigned i = 0; i < m_len; i++) {
1513         it.advance(i + 1, &glyphBuffer);
1514         widths[i] = it.m_runWidthSoFar - lastWidth;
1515         lastWidth = it.m_runWidthSoFar;
1516     }
1517 }
1518 
constructTextRun(RenderStyle * style,const Font & font,StringBuilder * charactersWithHyphen) const1519 TextRun InlineTextBox::constructTextRun(RenderStyle* style, const Font& font, StringBuilder* charactersWithHyphen) const
1520 {
1521     ASSERT(style);
1522     ASSERT(textRenderer().text());
1523 
1524     StringView string = textRenderer().text().createView();
1525     unsigned startPos = start();
1526     unsigned length = len();
1527 
1528     if (string.length() != length || startPos)
1529         string.narrow(startPos, length);
1530 
1531     return constructTextRun(style, font, string, textRenderer().textLength() - startPos, charactersWithHyphen);
1532 }
1533 
constructTextRun(RenderStyle * style,const Font & font,StringView string,int maximumLength,StringBuilder * charactersWithHyphen) const1534 TextRun InlineTextBox::constructTextRun(RenderStyle* style, const Font& font, StringView string, int maximumLength, StringBuilder* charactersWithHyphen) const
1535 {
1536     ASSERT(style);
1537 
1538     if (charactersWithHyphen) {
1539         const AtomicString& hyphenString = style->hyphenString();
1540         charactersWithHyphen->reserveCapacity(string.length() + hyphenString.length());
1541         charactersWithHyphen->append(string);
1542         charactersWithHyphen->append(hyphenString);
1543         string = charactersWithHyphen->toString().createView();
1544         maximumLength = string.length();
1545     }
1546 
1547     ASSERT(maximumLength >= static_cast<int>(string.length()));
1548 
1549     TextRun run(string, textPos(), expansion(), expansionBehavior(), direction(), dirOverride() || style->rtlOrdering() == VisualOrder, !textRenderer().canUseSimpleFontCodePath());
1550     run.setTabSize(!style->collapseWhiteSpace(), style->tabSize());
1551     run.setCharacterScanForCodePath(!textRenderer().canUseSimpleFontCodePath());
1552     if (textRunNeedsRenderingContext(font))
1553         run.setRenderingContext(SVGTextRunRenderingContext::create(&textRenderer()));
1554 
1555     // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring.
1556     run.setCharactersLength(maximumLength);
1557     ASSERT(run.charactersLength() >= run.length());
1558     return run;
1559 }
1560 
constructTextRunForInspector(RenderStyle * style,const Font & font) const1561 TextRun InlineTextBox::constructTextRunForInspector(RenderStyle* style, const Font& font) const
1562 {
1563     return InlineTextBox::constructTextRun(style, font);
1564 }
1565 
1566 #ifndef NDEBUG
1567 
boxName() const1568 const char* InlineTextBox::boxName() const
1569 {
1570     return "InlineTextBox";
1571 }
1572 
showBox(int printedCharacters) const1573 void InlineTextBox::showBox(int printedCharacters) const
1574 {
1575     const RenderText& obj = toRenderText(renderer());
1576     String value = obj.text();
1577     value = value.substring(start(), len());
1578     value.replaceWithLiteral('\\', "\\\\");
1579     value.replaceWithLiteral('\n', "\\n");
1580     printedCharacters += fprintf(stderr, "%s\t%p", boxName(), this);
1581     for (; printedCharacters < showTreeCharacterOffset; printedCharacters++)
1582         fputc(' ', stderr);
1583     printedCharacters = fprintf(stderr, "\t%s %p", obj.renderName(), &obj);
1584     const int rendererCharacterOffset = 24;
1585     for (; printedCharacters < rendererCharacterOffset; printedCharacters++)
1586         fputc(' ', stderr);
1587     fprintf(stderr, "(%d,%d) \"%s\"\n", start(), start() + len(), value.utf8().data());
1588 }
1589 
1590 #endif
1591 
1592 } // namespace WebCore
1593