1/*
2 * (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 2000 Dirk Mueller (mueller@kde.org)
4 * Copyright (C) 2004-2014 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 "Chrome.h"
27#include "ChromeClient.h"
28#include "DashArray.h"
29#include "Document.h"
30#include "DocumentMarkerController.h"
31#include "Editor.h"
32#include "EllipsisBox.h"
33#include "FontCache.h"
34#include "Frame.h"
35#include "GraphicsContext.h"
36#include "HitTestResult.h"
37#include "ImageBuffer.h"
38#include "InlineTextBoxStyle.h"
39#include "Page.h"
40#include "PaintInfo.h"
41#include "RenderedDocumentMarker.h"
42#include "RenderBlock.h"
43#include "RenderCombineText.h"
44#include "RenderLineBreak.h"
45#include "RenderRubyRun.h"
46#include "RenderRubyText.h"
47#include "RenderTheme.h"
48#include "RenderView.h"
49#include "Settings.h"
50#include "SVGTextRunRenderingContext.h"
51#include "Text.h"
52#include "TextPaintStyle.h"
53#include "TextPainter.h"
54#include "break_lines.h"
55#include <stdio.h>
56#include <wtf/text/CString.h>
57
58namespace WebCore {
59
60struct SameSizeAsInlineTextBox : public InlineBox {
61    unsigned variables[1];
62    unsigned short variables2[2];
63    void* pointers[2];
64};
65
66COMPILE_ASSERT(sizeof(InlineTextBox) == sizeof(SameSizeAsInlineTextBox), InlineTextBox_should_stay_small);
67
68typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap;
69static InlineTextBoxOverflowMap* gTextBoxesWithOverflow;
70
71#if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
72static bool compareTuples(std::pair<float, float> l, std::pair<float, float> r)
73{
74    return l.first < r.first;
75}
76
77static DashArray translateIntersectionPointsToSkipInkBoundaries(const DashArray& intersections, float dilationAmount, float totalWidth)
78{
79    ASSERT(!(intersections.size() % 2));
80
81    // Step 1: Make pairs so we can sort based on range starting-point. We dilate the ranges in this step as well.
82    Vector<std::pair<float, float>> tuples;
83    for (auto i = intersections.begin(); i != intersections.end(); i++, i++)
84        tuples.append(std::make_pair(*i - dilationAmount, *(i + 1) + dilationAmount));
85    std::sort(tuples.begin(), tuples.end(), &compareTuples);
86
87    // Step 2: Deal with intersecting ranges.
88    Vector<std::pair<float, float>> intermediateTuples;
89    if (tuples.size() >= 2) {
90        intermediateTuples.append(*tuples.begin());
91        for (auto i = tuples.begin() + 1; i != tuples.end(); i++) {
92            float& firstEnd = intermediateTuples.last().second;
93            float secondStart = i->first;
94            float secondEnd = i->second;
95            if (secondStart <= firstEnd && secondEnd <= firstEnd) {
96                // Ignore this range completely
97            } else if (secondStart <= firstEnd)
98                firstEnd = secondEnd;
99            else
100                intermediateTuples.append(*i);
101        }
102    } else
103        intermediateTuples = tuples;
104
105    // Step 3: Output the space between the ranges, but only if the space warrants an underline.
106    float previous = 0;
107    DashArray result;
108    for (const auto& tuple : intermediateTuples) {
109        if (tuple.first - previous > dilationAmount) {
110            result.append(previous);
111            result.append(tuple.first);
112        }
113        previous = tuple.second;
114    }
115    if (totalWidth - previous > dilationAmount) {
116        result.append(previous);
117        result.append(totalWidth);
118    }
119
120    return result;
121}
122
123static void drawSkipInkUnderline(TextPainter& textPainter, GraphicsContext& context, FloatPoint localOrigin, float underlineOffset, float width, bool isPrinting, bool doubleLines)
124{
125    FloatPoint adjustedLocalOrigin = localOrigin;
126    adjustedLocalOrigin.move(0, underlineOffset);
127    FloatRect underlineBoundingBox = context.computeLineBoundsForText(adjustedLocalOrigin, width, isPrinting);
128    DashArray intersections = textPainter.dashesForIntersectionsWithRect(underlineBoundingBox);
129    DashArray a = translateIntersectionPointsToSkipInkBoundaries(intersections, underlineBoundingBox.height(), width);
130
131    ASSERT(!(a.size() % 2));
132    context.drawLinesForText(adjustedLocalOrigin, a, isPrinting, doubleLines);
133}
134#endif
135
136InlineTextBox::~InlineTextBox()
137{
138    if (!knownToHaveNoOverflow() && gTextBoxesWithOverflow)
139        gTextBoxesWithOverflow->remove(this);
140}
141
142void InlineTextBox::markDirty(bool dirty)
143{
144    if (dirty) {
145        m_len = 0;
146        m_start = 0;
147    }
148    InlineBox::markDirty(dirty);
149}
150
151LayoutRect InlineTextBox::logicalOverflowRect() const
152{
153    if (knownToHaveNoOverflow() || !gTextBoxesWithOverflow)
154        return enclosingIntRect(logicalFrameRect());
155    return gTextBoxesWithOverflow->get(this);
156}
157
158void InlineTextBox::setLogicalOverflowRect(const LayoutRect& rect)
159{
160    ASSERT(!knownToHaveNoOverflow());
161    if (!gTextBoxesWithOverflow)
162        gTextBoxesWithOverflow = new InlineTextBoxOverflowMap;
163    gTextBoxesWithOverflow->add(this, rect);
164}
165
166int InlineTextBox::baselinePosition(FontBaseline baselineType) const
167{
168    if (!parent())
169        return 0;
170    if (&parent()->renderer() == renderer().parent())
171        return parent()->baselinePosition(baselineType);
172    return toRenderBoxModelObject(renderer().parent())->baselinePosition(baselineType, isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
173}
174
175LayoutUnit InlineTextBox::lineHeight() const
176{
177    if (!renderer().parent())
178        return 0;
179    if (&parent()->renderer() == renderer().parent())
180        return parent()->lineHeight();
181    return toRenderBoxModelObject(renderer().parent())->lineHeight(isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
182}
183
184LayoutUnit InlineTextBox::selectionTop() const
185{
186    return root().selectionTop();
187}
188
189LayoutUnit InlineTextBox::selectionBottom() const
190{
191    return root().selectionBottom();
192}
193
194LayoutUnit InlineTextBox::selectionHeight() const
195{
196    return root().selectionHeight();
197}
198
199bool InlineTextBox::isSelected(int startPos, int endPos) const
200{
201    int sPos = std::max(startPos - m_start, 0);
202    int ePos = std::min(endPos - m_start, static_cast<int>(m_len));
203    return (sPos < ePos);
204}
205
206RenderObject::SelectionState InlineTextBox::selectionState()
207{
208    RenderObject::SelectionState state = renderer().selectionState();
209    if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
210        int startPos, endPos;
211        renderer().selectionStartEnd(startPos, endPos);
212        // The position after a hard line break is considered to be past its end.
213        int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0);
214
215        bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
216        bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
217        if (start && end)
218            state = RenderObject::SelectionBoth;
219        else if (start)
220            state = RenderObject::SelectionStart;
221        else if (end)
222            state = RenderObject::SelectionEnd;
223        else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
224                 (state == RenderObject::SelectionStart || endPos > lastSelectable))
225            state = RenderObject::SelectionInside;
226        else if (state == RenderObject::SelectionBoth)
227            state = RenderObject::SelectionNone;
228    }
229
230    // If there are ellipsis following, make sure their selection is updated.
231    if (m_truncation != cNoTruncation && root().ellipsisBox()) {
232        EllipsisBox* ellipsis = root().ellipsisBox();
233        if (state != RenderObject::SelectionNone) {
234            int start, end;
235            selectionStartEnd(start, end);
236            // The ellipsis should be considered to be selected if the end of
237            // the selection is past the beginning of the truncation and the
238            // beginning of the selection is before or at the beginning of the
239            // truncation.
240            ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ?
241                RenderObject::SelectionInside : RenderObject::SelectionNone);
242        } else
243            ellipsis->setSelectionState(RenderObject::SelectionNone);
244    }
245
246    return state;
247}
248
249static const Font& fontToUse(const RenderStyle& style, const RenderText& renderer)
250{
251    if (style.hasTextCombine() && renderer.isCombineText()) {
252        const RenderCombineText& textCombineRenderer = toRenderCombineText(renderer);
253        if (textCombineRenderer.isCombined())
254            return textCombineRenderer.textCombineFont();
255    }
256    return style.font();
257}
258
259LayoutRect InlineTextBox::localSelectionRect(int startPos, int endPos) const
260{
261    int sPos = std::max(startPos - m_start, 0);
262    int ePos = std::min(endPos - m_start, (int)m_len);
263
264    if (sPos > ePos)
265        return LayoutRect();
266
267    FontCachePurgePreventer fontCachePurgePreventer;
268
269    LayoutUnit selectionTop = this->selectionTop();
270    LayoutUnit selectionHeight = this->selectionHeight();
271    const RenderStyle& lineStyle = this->lineStyle();
272    const Font& font = fontToUse(lineStyle, renderer());
273
274    String hyphenatedStringBuffer;
275    bool respectHyphen = ePos == m_len && hasHyphen();
276    TextRun textRun = constructTextRun(lineStyle, font, respectHyphen ? &hyphenatedStringBuffer : 0);
277    if (respectHyphen)
278        endPos = textRun.length();
279
280    LayoutRect selectionRect = LayoutRect(LayoutPoint(logicalLeft(), selectionTop), LayoutSize(m_logicalWidth, selectionHeight));
281    // Avoid computing the font width when the entire line box is selected as an optimization.
282    if (sPos || ePos != static_cast<int>(m_len))
283        font.adjustSelectionRectForText(textRun, selectionRect, sPos, ePos);
284    IntRect snappedSelectionRect = enclosingIntRect(selectionRect);
285    LayoutUnit logicalWidth = snappedSelectionRect.width();
286    if (snappedSelectionRect.x() > logicalRight())
287        logicalWidth  = 0;
288    else if (snappedSelectionRect.maxX() > logicalRight())
289        logicalWidth = logicalRight() - snappedSelectionRect.x();
290
291    LayoutPoint topPoint = isHorizontal() ? LayoutPoint(snappedSelectionRect.x(), selectionTop) : LayoutPoint(selectionTop, snappedSelectionRect.x());
292    LayoutUnit width = isHorizontal() ? logicalWidth : selectionHeight;
293    LayoutUnit height = isHorizontal() ? selectionHeight : logicalWidth;
294
295    return LayoutRect(topPoint, LayoutSize(width, height));
296}
297
298void InlineTextBox::deleteLine()
299{
300    renderer().removeTextBox(*this);
301    delete this;
302}
303
304void InlineTextBox::extractLine()
305{
306    if (extracted())
307        return;
308
309    renderer().extractTextBox(*this);
310}
311
312void InlineTextBox::attachLine()
313{
314    if (!extracted())
315        return;
316
317    renderer().attachTextBox(*this);
318}
319
320float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, float &truncatedWidth, bool& foundBox)
321{
322    if (foundBox) {
323        m_truncation = cFullTruncation;
324        return -1;
325    }
326
327    // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
328    float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
329
330    // Criteria for full truncation:
331    // LTR: the left edge of the ellipsis is to the left of our text run.
332    // RTL: the right edge of the ellipsis is to the right of our text run.
333    bool ltrFullTruncation = flowIsLTR && ellipsisX <= left();
334    bool rtlFullTruncation = !flowIsLTR && ellipsisX >= left() + logicalWidth();
335    if (ltrFullTruncation || rtlFullTruncation) {
336        // Too far.  Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
337        m_truncation = cFullTruncation;
338        foundBox = true;
339        return -1;
340    }
341
342    bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < right());
343    bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > left());
344    if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
345        foundBox = true;
346
347        // The inline box may have different directionality than it's parent.  Since truncation
348        // behavior depends both on both the parent and the inline block's directionality, we
349        // must keep track of these separately.
350        bool ltr = isLeftToRightDirection();
351        if (ltr != flowIsLTR) {
352          // Width in pixels of the visible portion of the box, excluding the ellipsis.
353          int visibleBoxWidth = visibleRightEdge - visibleLeftEdge  - ellipsisWidth;
354          ellipsisX = ltr ? left() + visibleBoxWidth : right() - visibleBoxWidth;
355        }
356
357        int offset = offsetForPosition(ellipsisX, false);
358        if (offset == 0) {
359            // No characters should be rendered.  Set ourselves to full truncation and place the ellipsis at the min of our start
360            // and the ellipsis edge.
361            m_truncation = cFullTruncation;
362            truncatedWidth += ellipsisWidth;
363            return flowIsLTR ? std::min(ellipsisX, x()) : std::max(ellipsisX, right() - ellipsisWidth);
364        }
365
366        // Set the truncation index on the text run.
367        m_truncation = offset;
368
369        // If we got here that means that we were only partially truncated and we need to return the pixel offset at which
370        // to place the ellipsis.
371        float widthOfVisibleText = renderer().width(m_start, offset, textPos(), isFirstLine());
372
373        // The ellipsis needs to be placed just after the last visible character.
374        // Where "after" is defined by the flow directionality, not the inline
375        // box directionality.
376        // e.g. In the case of an LTR inline box truncated in an RTL flow then we can
377        // have a situation such as |Hello| -> |...He|
378        truncatedWidth += widthOfVisibleText + ellipsisWidth;
379        if (flowIsLTR)
380            return left() + widthOfVisibleText;
381        else
382            return right() - widthOfVisibleText - ellipsisWidth;
383    }
384    truncatedWidth += logicalWidth();
385    return -1;
386}
387
388
389
390bool InlineTextBox::isLineBreak() const
391{
392    return renderer().style().preserveNewline() && len() == 1 && (*renderer().text())[start()] == '\n';
393}
394
395bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/)
396{
397    if (!visibleToHitTesting())
398        return false;
399
400    if (isLineBreak())
401        return false;
402
403    if (m_truncation == cFullTruncation)
404        return false;
405
406    FloatRect rect(locationIncludingFlipping(), size());
407    // Make sure truncated text is ignored while hittesting.
408    if (m_truncation != cNoTruncation) {
409        LayoutUnit widthOfVisibleText = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
410
411        if (isHorizontal())
412            renderer().style().isLeftToRightDirection() ? rect.setWidth(widthOfVisibleText) : rect.shiftXEdgeTo(right() - widthOfVisibleText);
413        else
414            rect.setHeight(widthOfVisibleText);
415    }
416
417    rect.moveBy(accumulatedOffset);
418
419    if (locationInContainer.intersects(rect)) {
420        renderer().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset)));
421        if (!result.addNodeToRectBasedTestResult(renderer().textNode(), request, locationInContainer, rect))
422            return true;
423    }
424    return false;
425}
426
427FloatSize InlineTextBox::applyShadowToGraphicsContext(GraphicsContext* context, const ShadowData* shadow, const FloatRect& textRect, bool stroked, bool opaque, bool horizontal)
428{
429    if (!shadow)
430        return FloatSize();
431
432    FloatSize extraOffset;
433    int shadowX = horizontal ? shadow->x() : shadow->y();
434    int shadowY = horizontal ? shadow->y() : -shadow->x();
435    FloatSize shadowOffset(shadowX, shadowY);
436    int shadowRadius = shadow->radius();
437    const Color& shadowColor = shadow->color();
438
439    if (shadow->next() || stroked || !opaque) {
440        FloatRect shadowRect(textRect);
441        shadowRect.inflate(shadow->paintingExtent());
442        shadowRect.move(shadowOffset);
443        context->save();
444        context->clip(shadowRect);
445
446        extraOffset = FloatSize(0, 2 * textRect.height() + std::max(0.0f, shadowOffset.height()) + shadowRadius);
447        shadowOffset -= extraOffset;
448    }
449
450    context->setShadow(shadowOffset, shadowRadius, shadowColor, context->fillColorSpace());
451    return extraOffset;
452}
453
454static inline bool emphasisPositionHasNeitherLeftNorRight(TextEmphasisPosition emphasisPosition)
455{
456    return !(emphasisPosition & TextEmphasisPositionLeft) && !(emphasisPosition & TextEmphasisPositionRight);
457}
458
459bool InlineTextBox::emphasisMarkExistsAndIsAbove(const RenderStyle& style, bool& above) const
460{
461    // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
462    if (style.textEmphasisMark() == TextEmphasisMarkNone)
463        return false;
464
465    TextEmphasisPosition emphasisPosition = style.textEmphasisPosition();
466    ASSERT(!((emphasisPosition & TextEmphasisPositionOver) && (emphasisPosition & TextEmphasisPositionUnder)));
467    ASSERT(!((emphasisPosition & TextEmphasisPositionLeft) && (emphasisPosition & TextEmphasisPositionRight)));
468
469    if (emphasisPositionHasNeitherLeftNorRight(emphasisPosition))
470        above = emphasisPosition & TextEmphasisPositionOver;
471    else if (style.isHorizontalWritingMode())
472        above = emphasisPosition & TextEmphasisPositionOver;
473    else
474        above = emphasisPosition & TextEmphasisPositionRight;
475
476    if ((style.isHorizontalWritingMode() && (emphasisPosition & TextEmphasisPositionUnder))
477        || (!style.isHorizontalWritingMode() && (emphasisPosition & TextEmphasisPositionLeft)))
478        return true; // Ruby text is always over, so it cannot suppress emphasis marks under.
479
480    RenderBlock* containingBlock = renderer().containingBlock();
481    if (!containingBlock->isRubyBase())
482        return true; // This text is not inside a ruby base, so it does not have ruby text over it.
483
484    if (!containingBlock->parent()->isRubyRun())
485        return true; // Cannot get the ruby text.
486
487    RenderRubyText* rubyText = toRenderRubyRun(containingBlock->parent())->rubyText();
488
489    // The emphasis marks over are suppressed only if there is a ruby text box and it not empty.
490    return !rubyText || !rubyText->hasLines();
491}
492
493void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/)
494{
495    if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer().style().visibility() != VISIBLE
496        || m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len)
497        return;
498
499    ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
500
501    LayoutUnit logicalLeftSide = logicalLeftVisualOverflow();
502    LayoutUnit logicalRightSide = logicalRightVisualOverflow();
503    LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y());
504    LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide;
505
506    LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
507    LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
508
509    FloatPoint adjustedPaintOffset = roundedForPainting(paintOffset, renderer().document().deviceScaleFactor());
510
511    if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
512        return;
513
514    bool isPrinting = renderer().document().printing();
515
516    // Determine whether or not we're selected.
517    bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
518    if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
519        // When only painting the selection, don't bother to paint if there is none.
520        return;
521
522    if (m_truncation != cNoTruncation) {
523        if (renderer().containingBlock()->style().isLeftToRightDirection() != isLeftToRightDirection()) {
524            // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
525            // at which we start drawing text.
526            // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
527            // |Hello|CBA| -> |...He|CBA|
528            // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
529            // farther to the right.
530            // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
531            // truncated string i.e.  |Hello|CBA| -> |...lo|CBA|
532            LayoutUnit widthOfVisibleText = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
533            LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
534            LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0);
535            adjustedPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
536        }
537    }
538
539    GraphicsContext* context = paintInfo.context;
540
541    const RenderStyle& lineStyle = this->lineStyle();
542
543    adjustedPaintOffset.move(0, lineStyle.isHorizontalWritingMode() ? 0 : -logicalHeight());
544
545    FloatPoint boxOrigin = locationIncludingFlipping();
546    boxOrigin.move(adjustedPaintOffset.x(), adjustedPaintOffset.y());
547    FloatRect boxRect(boxOrigin, FloatSize(logicalWidth(), logicalHeight()));
548
549    RenderCombineText* combinedText = lineStyle.hasTextCombine() && renderer().isCombineText() && toRenderCombineText(renderer()).isCombined() ? &toRenderCombineText(renderer()) : 0;
550
551    bool shouldRotate = !isHorizontal() && !combinedText;
552    if (shouldRotate)
553        context->concatCTM(rotation(boxRect, Clockwise));
554
555    // Determine whether or not we have composition underlines to draw.
556    bool containsComposition = renderer().textNode() && renderer().frame().editor().compositionNode() == renderer().textNode();
557    bool useCustomUnderlines = containsComposition && renderer().frame().editor().compositionUsesCustomUnderlines();
558
559    // Determine the text colors and selection colors.
560    TextPaintStyle textPaintStyle = computeTextPaintStyle(renderer(), lineStyle, paintInfo);
561
562    bool paintSelectedTextOnly;
563    bool paintSelectedTextSeparately;
564    const ShadowData* selectionShadow;
565    TextPaintStyle selectionPaintStyle = computeTextSelectionPaintStyle(textPaintStyle, renderer(), lineStyle, paintInfo, paintSelectedTextOnly, paintSelectedTextSeparately, selectionShadow);
566
567    // Set our font.
568    const Font& font = fontToUse(lineStyle, renderer());
569
570    FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());
571
572    if (combinedText)
573        combinedText->adjustTextOrigin(textOrigin, boxRect);
574
575    // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
576    // and composition underlines.
577    if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
578        if (containsComposition && !useCustomUnderlines)
579            paintCompositionBackground(context, boxOrigin, lineStyle, font,
580                renderer().frame().editor().compositionStart(),
581                renderer().frame().editor().compositionEnd());
582
583        paintDocumentMarkers(context, boxOrigin, lineStyle, font, true);
584
585        if (haveSelection && !useCustomUnderlines)
586            paintSelection(context, boxOrigin, lineStyle, font, selectionPaintStyle.fillColor);
587    }
588
589    if (Page* page = renderer().frame().page()) {
590        // FIXME: Right now, InlineTextBoxes never call addRelevantUnpaintedObject() even though they might
591        // legitimately be unpainted if they are waiting on a slow-loading web font. We should fix that, and
592        // when we do, we will have to account for the fact the InlineTextBoxes do not always have unique
593        // renderers and Page currently relies on each unpainted object having a unique renderer.
594        if (paintInfo.phase == PaintPhaseForeground)
595            page->addRelevantRepaintedObject(&renderer(), IntRect(boxOrigin.x(), boxOrigin.y(), logicalWidth(), logicalHeight()));
596    }
597
598    // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
599    int length = m_len;
600    int maximumLength;
601    String string;
602    if (!combinedText) {
603        string = renderer().text();
604        if (static_cast<unsigned>(length) != string.length() || m_start) {
605            ASSERT_WITH_SECURITY_IMPLICATION(static_cast<unsigned>(m_start + length) <= string.length());
606            string = string.substringSharingImpl(m_start, length);
607        }
608        maximumLength = renderer().textLength() - m_start;
609    } else {
610        combinedText->getStringToRender(m_start, string, length);
611        maximumLength = length;
612    }
613
614    String hyphenatedStringBuffer;
615    TextRun textRun = constructTextRun(lineStyle, font, string, maximumLength, hasHyphen() ? &hyphenatedStringBuffer : nullptr);
616    if (hasHyphen())
617        length = textRun.length();
618
619    int sPos = 0;
620    int ePos = 0;
621    if (haveSelection && (paintSelectedTextOnly || paintSelectedTextSeparately))
622        selectionStartEnd(sPos, ePos);
623
624    if (m_truncation != cNoTruncation) {
625        sPos = std::min<int>(sPos, m_truncation);
626        ePos = std::min<int>(ePos, m_truncation);
627        length = m_truncation;
628    }
629
630    int emphasisMarkOffset = 0;
631    bool emphasisMarkAbove;
632    bool hasTextEmphasis = emphasisMarkExistsAndIsAbove(lineStyle, emphasisMarkAbove);
633    const AtomicString& emphasisMark = hasTextEmphasis ? lineStyle.textEmphasisMarkString() : nullAtom;
634    if (!emphasisMark.isEmpty())
635        emphasisMarkOffset = emphasisMarkAbove ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
636
637    const ShadowData* textShadow = paintInfo.forceBlackText() ? 0 : lineStyle.textShadow();
638
639    TextPainter textPainter(*context, paintSelectedTextOnly, paintSelectedTextSeparately, font, sPos, ePos, length, emphasisMark, combinedText, textRun, boxRect, textOrigin, emphasisMarkOffset, textShadow, selectionShadow, isHorizontal(), textPaintStyle, selectionPaintStyle);
640    textPainter.paintText();
641
642    // Paint decorations
643    TextDecoration textDecorations = lineStyle.textDecorationsInEffect();
644    if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) {
645        updateGraphicsContext(*context, textPaintStyle);
646        if (combinedText)
647            context->concatCTM(rotation(boxRect, Clockwise));
648        paintDecoration(*context, boxOrigin, textDecorations, lineStyle.textDecorationStyle(), textShadow, textPainter);
649        if (combinedText)
650            context->concatCTM(rotation(boxRect, Counterclockwise));
651    }
652
653    if (paintInfo.phase == PaintPhaseForeground) {
654        paintDocumentMarkers(context, boxOrigin, lineStyle, font, false);
655
656        if (useCustomUnderlines) {
657            const Vector<CompositionUnderline>& underlines = renderer().frame().editor().customCompositionUnderlines();
658            size_t numUnderlines = underlines.size();
659
660            for (size_t index = 0; index < numUnderlines; ++index) {
661                const CompositionUnderline& underline = underlines[index];
662
663                if (underline.endOffset <= start())
664                    // underline is completely before this run.  This might be an underline that sits
665                    // before the first run we draw, or underlines that were within runs we skipped
666                    // due to truncation.
667                    continue;
668
669                if (underline.startOffset <= end()) {
670                    // underline intersects this run.  Paint it.
671                    paintCompositionUnderline(context, boxOrigin, underline);
672                    if (underline.endOffset > end() + 1)
673                        // underline also runs into the next run. Bail now, no more marker advancement.
674                        break;
675                } else
676                    // underline is completely after this run, bail.  A later run will paint it.
677                    break;
678            }
679        }
680    }
681
682    if (shouldRotate)
683        context->concatCTM(rotation(boxRect, Counterclockwise));
684}
685
686void InlineTextBox::selectionStartEnd(int& sPos, int& ePos)
687{
688    int startPos, endPos;
689    if (renderer().selectionState() == RenderObject::SelectionInside) {
690        startPos = 0;
691        endPos = renderer().textLength();
692    } else {
693        renderer().selectionStartEnd(startPos, endPos);
694        if (renderer().selectionState() == RenderObject::SelectionStart)
695            endPos = renderer().textLength();
696        else if (renderer().selectionState() == RenderObject::SelectionEnd)
697            startPos = 0;
698    }
699
700    sPos = std::max(startPos - m_start, 0);
701    ePos = std::min(endPos - m_start, (int)m_len);
702}
703
704void InlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& boxOrigin, const RenderStyle& style, const Font& font, Color textColor)
705{
706#if ENABLE(TEXT_SELECTION)
707    if (context->paintingDisabled())
708        return;
709
710    // See if we have a selection to paint at all.
711    int sPos, ePos;
712    selectionStartEnd(sPos, ePos);
713    if (sPos >= ePos)
714        return;
715
716    Color c = renderer().selectionBackgroundColor();
717    if (!c.isValid() || c.alpha() == 0)
718        return;
719
720    // If the text color ends up being the same as the selection background, invert the selection
721    // background.
722    if (textColor == c)
723        c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
724
725    GraphicsContextStateSaver stateSaver(*context);
726    updateGraphicsContext(*context, TextPaintStyle(c, style.colorSpace())); // Don't draw text at all!
727
728    // If the text is truncated, let the thing being painted in the truncation
729    // draw its own highlight.
730    int length = m_truncation != cNoTruncation ? m_truncation : m_len;
731    String string = renderer().text();
732
733    if (string.length() != static_cast<unsigned>(length) || m_start) {
734        ASSERT_WITH_SECURITY_IMPLICATION(static_cast<unsigned>(m_start + length) <= string.length());
735        string = string.substringSharingImpl(m_start, length);
736    }
737
738    String hyphenatedStringBuffer;
739    bool respectHyphen = ePos == length && hasHyphen();
740    TextRun textRun = constructTextRun(style, font, string, renderer().textLength() - m_start, respectHyphen ? &hyphenatedStringBuffer : nullptr);
741    if (respectHyphen)
742        ePos = textRun.length();
743
744    const RootInlineBox& rootBox = root();
745    LayoutUnit selectionBottom = rootBox.selectionBottom();
746    LayoutUnit selectionTop = rootBox.selectionTopAdjustedForPrecedingBlock();
747
748    LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop;
749    LayoutUnit selectionHeight = std::max<LayoutUnit>(0, selectionBottom - selectionTop);
750
751    LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selectionHeight);
752    font.adjustSelectionRectForText(textRun, selectionRect, sPos, ePos);
753    context->fillRect(directionalPixelSnappedForPainting(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), c, style.colorSpace());
754#else
755    UNUSED_PARAM(context);
756    UNUSED_PARAM(boxOrigin);
757    UNUSED_PARAM(style);
758    UNUSED_PARAM(font);
759    UNUSED_PARAM(textColor);
760#endif
761}
762
763void InlineTextBox::paintCompositionBackground(GraphicsContext* context, const FloatPoint& boxOrigin, const RenderStyle& style, const Font& font, int startPos, int endPos)
764{
765    int offset = m_start;
766    int sPos = std::max(startPos - offset, 0);
767    int ePos = std::min(endPos - offset, (int)m_len);
768
769    if (sPos >= ePos)
770        return;
771
772    GraphicsContextStateSaver stateSaver(*context);
773
774#if !PLATFORM(IOS)
775    // FIXME: Is this color still applicable as of Mavericks? for non-Mac ports? We should
776    // be able to move this color information to RenderStyle.
777    Color c = Color(225, 221, 85);
778#else
779    Color c = style.compositionFillColor();
780#endif
781
782    updateGraphicsContext(*context, TextPaintStyle(c, style.colorSpace())); // Don't draw text at all!
783
784    LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
785    LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, 0, selectionHeight());
786    TextRun textRun = constructTextRun(style, font);
787    font.adjustSelectionRectForText(textRun, selectionRect, sPos, ePos);
788    context->fillRect(directionalPixelSnappedForPainting(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), c, style.colorSpace());
789}
790
791static StrokeStyle textDecorationStyleToStrokeStyle(TextDecorationStyle decorationStyle)
792{
793    StrokeStyle strokeStyle = SolidStroke;
794    switch (decorationStyle) {
795    case TextDecorationStyleSolid:
796        strokeStyle = SolidStroke;
797        break;
798    case TextDecorationStyleDouble:
799        strokeStyle = DoubleStroke;
800        break;
801    case TextDecorationStyleDotted:
802        strokeStyle = DottedStroke;
803        break;
804    case TextDecorationStyleDashed:
805        strokeStyle = DashedStroke;
806        break;
807    case TextDecorationStyleWavy:
808        strokeStyle = WavyStroke;
809        break;
810    }
811
812    return strokeStyle;
813}
814
815static void adjustStepToDecorationLength(float& step, float& controlPointDistance, float length)
816{
817    ASSERT(step > 0);
818
819    if (length <= 0)
820        return;
821
822    unsigned stepCount = static_cast<unsigned>(length / step);
823
824    // Each Bezier curve starts at the same pixel that the previous one
825    // ended. We need to subtract (stepCount - 1) pixels when calculating the
826    // length covered to account for that.
827    float uncoveredLength = length - (stepCount * step - (stepCount - 1));
828    float adjustment = uncoveredLength / stepCount;
829    step += adjustment;
830    controlPointDistance += adjustment;
831}
832
833/*
834 * Draw one cubic Bezier curve and repeat the same pattern long the the decoration's axis.
835 * The start point (p1), controlPoint1, controlPoint2 and end point (p2) of the Bezier curve
836 * form a diamond shape:
837 *
838 *                              step
839 *                         |-----------|
840 *
841 *                   controlPoint1
842 *                         +
843 *
844 *
845 *                  . .
846 *                .     .
847 *              .         .
848 * (x1, y1) p1 +           .            + p2 (x2, y2) - <--- Decoration's axis
849 *                          .         .               |
850 *                            .     .                 |
851 *                              . .                   | controlPointDistance
852 *                                                    |
853 *                                                    |
854 *                         +                          -
855 *                   controlPoint2
856 *
857 *             |-----------|
858 *                 step
859 */
860static void strokeWavyTextDecoration(GraphicsContext& context, FloatPoint& p1, FloatPoint& p2, float strokeThickness)
861{
862    context.adjustLineToPixelBoundaries(p1, p2, strokeThickness, context.strokeStyle());
863
864    Path path;
865    path.moveTo(p1);
866
867    float controlPointDistance;
868    float step;
869    getWavyStrokeParameters(strokeThickness, controlPointDistance, step);
870
871    bool isVerticalLine = (p1.x() == p2.x());
872
873    if (isVerticalLine) {
874        ASSERT(p1.x() == p2.x());
875
876        float xAxis = p1.x();
877        float y1;
878        float y2;
879
880        if (p1.y() < p2.y()) {
881            y1 = p1.y();
882            y2 = p2.y();
883        } else {
884            y1 = p2.y();
885            y2 = p1.y();
886        }
887
888        adjustStepToDecorationLength(step, controlPointDistance, y2 - y1);
889        FloatPoint controlPoint1(xAxis + controlPointDistance, 0);
890        FloatPoint controlPoint2(xAxis - controlPointDistance, 0);
891
892        for (float y = y1; y + 2 * step <= y2;) {
893            controlPoint1.setY(y + step);
894            controlPoint2.setY(y + step);
895            y += 2 * step;
896            path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y));
897        }
898    } else {
899        ASSERT(p1.y() == p2.y());
900
901        float yAxis = p1.y();
902        float x1;
903        float x2;
904
905        if (p1.x() < p2.x()) {
906            x1 = p1.x();
907            x2 = p2.x();
908        } else {
909            x1 = p2.x();
910            x2 = p1.x();
911        }
912
913        adjustStepToDecorationLength(step, controlPointDistance, x2 - x1);
914        FloatPoint controlPoint1(0, yAxis + controlPointDistance);
915        FloatPoint controlPoint2(0, yAxis - controlPointDistance);
916
917        for (float x = x1; x + 2 * step <= x2;) {
918            controlPoint1.setX(x + step);
919            controlPoint2.setX(x + step);
920            x += 2 * step;
921            path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis));
922        }
923    }
924
925    context.setShouldAntialias(true);
926    context.strokePath(path);
927}
928
929void InlineTextBox::paintDecoration(GraphicsContext& context, const FloatPoint& boxOrigin, TextDecoration decoration, TextDecorationStyle decorationStyle, const ShadowData* shadow, TextPainter& textPainter)
930{
931#if !ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
932    UNUSED_PARAM(textPainter);
933#endif
934
935    if (m_truncation == cFullTruncation)
936        return;
937
938    FloatPoint localOrigin = boxOrigin;
939
940    float width = m_logicalWidth;
941    if (m_truncation != cNoTruncation) {
942        width = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
943        if (!isLeftToRightDirection())
944            localOrigin.move(m_logicalWidth - width, 0);
945    }
946
947    // Get the text decoration colors.
948    Color underline, overline, linethrough;
949    renderer().getTextDecorationColors(decoration, underline, overline, linethrough, true);
950    if (isFirstLine())
951        renderer().getTextDecorationColors(decoration, underline, overline, linethrough, true, true);
952
953    // Use a special function for underlines to get the positioning exactly right.
954    bool isPrinting = renderer().document().printing();
955
956    float textDecorationThickness = textDecorationStrokeThickness(renderer().style().fontSize());
957    context.setStrokeThickness(textDecorationThickness);
958
959    bool linesAreOpaque = !isPrinting && (!(decoration & TextDecorationUnderline) || underline.alpha() == 255) && (!(decoration & TextDecorationOverline) || overline.alpha() == 255) && (!(decoration & TextDecorationLineThrough) || linethrough.alpha() == 255);
960
961    const RenderStyle& lineStyle = this->lineStyle();
962    int baseline = lineStyle.fontMetrics().ascent();
963
964    bool setClip = false;
965    int extraOffset = 0;
966    if (!linesAreOpaque && shadow && shadow->next()) {
967        FloatRect clipRect(localOrigin, FloatSize(width, baseline + 2));
968        for (const ShadowData* s = shadow; s; s = s->next()) {
969            int shadowExtent = s->paintingExtent();
970            FloatRect shadowRect(localOrigin, FloatSize(width, baseline + 2));
971            shadowRect.inflate(shadowExtent);
972            int shadowX = isHorizontal() ? s->x() : s->y();
973            int shadowY = isHorizontal() ? s->y() : -s->x();
974            shadowRect.move(shadowX, shadowY);
975            clipRect.unite(shadowRect);
976            extraOffset = std::max(extraOffset, std::max(0, shadowY) + shadowExtent);
977        }
978        context.save();
979        context.clip(clipRect);
980        extraOffset += baseline + 2;
981        localOrigin.move(0, extraOffset);
982        setClip = true;
983    }
984
985    ColorSpace colorSpace = renderer().style().colorSpace();
986    bool setShadow = false;
987
988    do {
989        if (shadow) {
990            if (!shadow->next()) {
991                // The last set of lines paints normally inside the clip.
992                localOrigin.move(0, -extraOffset);
993                extraOffset = 0;
994            }
995            int shadowX = isHorizontal() ? shadow->x() : shadow->y();
996            int shadowY = isHorizontal() ? shadow->y() : -shadow->x();
997            context.setShadow(FloatSize(shadowX, shadowY - extraOffset), shadow->radius(), shadow->color(), colorSpace);
998            setShadow = true;
999            shadow = shadow->next();
1000        }
1001
1002        float wavyOffset = wavyOffsetFromDecoration();
1003
1004        context.setStrokeStyle(textDecorationStyleToStrokeStyle(decorationStyle));
1005        // These decorations should match the visual overflows computed in visualOverflowForDecorations()
1006        if (decoration & TextDecorationUnderline) {
1007            context.setStrokeColor(underline, colorSpace);
1008            const int underlineOffset = computeUnderlineOffset(lineStyle.textUnderlinePosition(), lineStyle.fontMetrics(), this, textDecorationThickness);
1009
1010            switch (decorationStyle) {
1011            case TextDecorationStyleWavy: {
1012                FloatPoint start(localOrigin.x(), localOrigin.y() + underlineOffset + wavyOffset);
1013                FloatPoint end(localOrigin.x() + width, localOrigin.y() + underlineOffset + wavyOffset);
1014                strokeWavyTextDecoration(context, start, end, textDecorationThickness);
1015                break;
1016            }
1017            default:
1018#if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
1019                if ((lineStyle.textDecorationSkip() == TextDecorationSkipInk || lineStyle.textDecorationSkip() == TextDecorationSkipAuto) && isHorizontal()) {
1020                    if (!context.paintingDisabled()) {
1021                        drawSkipInkUnderline(textPainter, context, localOrigin, underlineOffset, width, isPrinting, decorationStyle == TextDecorationStyleDouble);
1022                    }
1023                } else
1024                    // FIXME: Need to support text-decoration-skip: none.
1025#endif // CSS3_TEXT_DECORATION_SKIP_INK
1026                    context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset), width, isPrinting, decorationStyle == TextDecorationStyleDouble);
1027            }
1028        }
1029        if (decoration & TextDecorationOverline) {
1030            context.setStrokeColor(overline, colorSpace);
1031            switch (decorationStyle) {
1032            case TextDecorationStyleWavy: {
1033                FloatPoint start(localOrigin.x(), localOrigin.y() - wavyOffset);
1034                FloatPoint end(localOrigin.x() + width, localOrigin.y() - wavyOffset);
1035                strokeWavyTextDecoration(context, start, end, textDecorationThickness);
1036                break;
1037            }
1038            default:
1039#if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
1040                if ((lineStyle.textDecorationSkip() == TextDecorationSkipInk || lineStyle.textDecorationSkip() == TextDecorationSkipAuto) && isHorizontal()) {
1041                    if (!context.paintingDisabled())
1042                        drawSkipInkUnderline(textPainter, context, localOrigin, 0, width, isPrinting, decorationStyle == TextDecorationStyleDouble);
1043                } else
1044                    // FIXME: Need to support text-decoration-skip: none.
1045#endif // CSS3_TEXT_DECORATION_SKIP_INK
1046                    context.drawLineForText(localOrigin, width, isPrinting, decorationStyle == TextDecorationStyleDouble);
1047            }
1048        }
1049        if (decoration & TextDecorationLineThrough) {
1050            context.setStrokeColor(linethrough, colorSpace);
1051            switch (decorationStyle) {
1052            case TextDecorationStyleWavy: {
1053                FloatPoint start(localOrigin.x(), localOrigin.y() + 2 * baseline / 3);
1054                FloatPoint end(localOrigin.x() + width, localOrigin.y() + 2 * baseline / 3);
1055                strokeWavyTextDecoration(context, start, end, textDecorationThickness);
1056                break;
1057            }
1058            default:
1059                context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + 2 * baseline / 3), width, isPrinting, decorationStyle == TextDecorationStyleDouble);
1060            }
1061        }
1062    } while (shadow);
1063
1064    if (setClip)
1065        context.restore();
1066    else if (setShadow)
1067        context.clearShadow();
1068}
1069
1070static GraphicsContext::DocumentMarkerLineStyle lineStyleForMarkerType(DocumentMarker::MarkerType markerType)
1071{
1072    switch (markerType) {
1073    case DocumentMarker::Spelling:
1074        return GraphicsContext::DocumentMarkerSpellingLineStyle;
1075    case DocumentMarker::Grammar:
1076        return GraphicsContext::DocumentMarkerGrammarLineStyle;
1077    case DocumentMarker::CorrectionIndicator:
1078        return GraphicsContext::DocumentMarkerAutocorrectionReplacementLineStyle;
1079    case DocumentMarker::DictationAlternatives:
1080        return GraphicsContext::DocumentMarkerDictationAlternativesLineStyle;
1081#if PLATFORM(IOS)
1082    case DocumentMarker::DictationPhraseWithAlternatives:
1083        // FIXME: Rename TextCheckingDictationPhraseWithAlternativesLineStyle and remove the PLATFORM(IOS)-guard.
1084        return GraphicsContext::TextCheckingDictationPhraseWithAlternativesLineStyle;
1085#endif
1086    default:
1087        ASSERT_NOT_REACHED();
1088        return GraphicsContext::DocumentMarkerSpellingLineStyle;
1089    }
1090}
1091
1092void InlineTextBox::paintDocumentMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, const RenderStyle& style, const Font& font, bool grammar)
1093{
1094    // Never print spelling/grammar markers (5327887)
1095    if (renderer().document().printing())
1096        return;
1097
1098    if (m_truncation == cFullTruncation)
1099        return;
1100
1101    float start = 0; // start of line to draw, relative to tx
1102    float width = m_logicalWidth; // how much line to draw
1103
1104    // Determine whether we need to measure text
1105    bool markerSpansWholeBox = true;
1106    if (m_start <= (int)marker->startOffset())
1107        markerSpansWholeBox = false;
1108    if ((end() + 1) != marker->endOffset()) // end points at the last char, not past it
1109        markerSpansWholeBox = false;
1110    if (m_truncation != cNoTruncation)
1111        markerSpansWholeBox = false;
1112
1113    bool isDictationMarker = marker->type() == DocumentMarker::DictationAlternatives;
1114    if (!markerSpansWholeBox || grammar || isDictationMarker) {
1115        int startPosition = std::max<int>(marker->startOffset() - m_start, 0);
1116        int endPosition = std::min<int>(marker->endOffset() - m_start, m_len);
1117
1118        if (m_truncation != cNoTruncation)
1119            endPosition = std::min<int>(endPosition, m_truncation);
1120
1121        // Calculate start & width
1122        int deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1123        int selHeight = selectionHeight();
1124        FloatPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY);
1125        TextRun run = constructTextRun(style, font);
1126
1127        LayoutRect selectionRect = LayoutRect(startPoint, FloatSize(0, selHeight));
1128        font.adjustSelectionRectForText(run, selectionRect, startPosition, endPosition);
1129        IntRect markerRect = enclosingIntRect(selectionRect);
1130        start = markerRect.x() - startPoint.x();
1131        width = markerRect.width();
1132
1133        // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to
1134        // display a toolTip. We don't do this for misspelling markers.
1135        if (grammar || isDictationMarker) {
1136            markerRect.move(-boxOrigin.x(), -boxOrigin.y());
1137            markerRect = renderer().localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1138            toRenderedDocumentMarker(marker)->setRenderedRect(markerRect);
1139        }
1140    }
1141
1142    // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to
1143    // make sure to fit within those bounds.  This means the top pixel(s) of the underline will overlap the
1144    // bottom pixel(s) of the glyphs in smaller font sizes.  The alternatives are to increase the line spacing (bad!!)
1145    // or decrease the underline thickness.  The overlap is actually the most useful, and matches what AppKit does.
1146    // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so
1147    // we pin to two pixels under the baseline.
1148    int lineThickness = cMisspellingLineThickness;
1149    int baseline = lineStyle().fontMetrics().ascent();
1150    int descent = logicalHeight() - baseline;
1151    int underlineOffset;
1152    if (descent <= (2 + lineThickness)) {
1153        // Place the underline at the very bottom of the text in small/medium fonts.
1154        underlineOffset = logicalHeight() - lineThickness;
1155    } else {
1156        // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
1157        underlineOffset = baseline + 2;
1158    }
1159    pt->drawLineForDocumentMarker(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, lineStyleForMarkerType(marker->type()));
1160}
1161
1162void InlineTextBox::paintTextMatchMarker(GraphicsContext* context, const FloatPoint& boxOrigin, DocumentMarker* marker, const RenderStyle& style, const Font& font)
1163{
1164    LayoutUnit selectionHeight = this->selectionHeight();
1165
1166    int sPos = std::max(marker->startOffset() - m_start, (unsigned)0);
1167    int ePos = std::min(marker->endOffset() - m_start, (unsigned)m_len);
1168    TextRun run = constructTextRun(style, font);
1169
1170    // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates.
1171    // FIXME: figure out how renderedRect and selectionRect are different.
1172    LayoutRect renderedRect = LayoutRect(LayoutPoint(x(), selectionTop()), FloatSize(0, selectionHeight));
1173    font.adjustSelectionRectForText(run, renderedRect, sPos, ePos);
1174    IntRect markerRect = enclosingIntRect(renderedRect);
1175    markerRect = renderer().localToAbsoluteQuad(FloatQuad(markerRect)).enclosingBoundingBox();
1176    toRenderedDocumentMarker(marker)->setRenderedRect(markerRect);
1177
1178    // Optionally highlight the text
1179    if (renderer().frame().editor().markedTextMatchesAreHighlighted()) {
1180        Color color = marker->activeMatch() ? renderer().theme().platformActiveTextSearchHighlightColor() : renderer().theme().platformInactiveTextSearchHighlightColor();
1181        GraphicsContextStateSaver stateSaver(*context);
1182        updateGraphicsContext(*context, TextPaintStyle(color, style.colorSpace())); // Don't draw text at all!
1183
1184        // Use same y positioning and height as for selection, so that when the selection and this highlight are on
1185        // the same word there are no pieces sticking out.
1186        LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1187        LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, 0, selectionHeight);
1188        font.adjustSelectionRectForText(run, selectionRect, sPos, ePos);
1189        context->fillRect(directionalPixelSnappedForPainting(selectionRect, renderer().document().deviceScaleFactor(), run.ltr()), color, style.colorSpace());
1190    }
1191}
1192
1193void InlineTextBox::computeRectForReplacementMarker(DocumentMarker* marker, const RenderStyle& style, const Font& font)
1194{
1195    // Replacement markers are not actually drawn, but their rects need to be computed for hit testing.
1196    LayoutUnit top = selectionTop();
1197    LayoutUnit h = selectionHeight();
1198
1199    int sPos = std::max(marker->startOffset() - m_start, (unsigned)0);
1200    int ePos = std::min(marker->endOffset() - m_start, (unsigned)m_len);
1201    TextRun run = constructTextRun(style, font);
1202
1203    // Compute and store the rect associated with this marker.
1204    LayoutRect selectionRect = LayoutRect(LayoutPoint(x(), top), LayoutSize(0, h));
1205    font.adjustSelectionRectForText(run, selectionRect, sPos, ePos);
1206    IntRect markerRect = enclosingIntRect(selectionRect);
1207    markerRect = renderer().localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1208    toRenderedDocumentMarker(marker)->setRenderedRect(markerRect);
1209}
1210
1211void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& boxOrigin, const RenderStyle& style, const Font& font, bool background)
1212{
1213    if (!renderer().textNode())
1214        return;
1215
1216    Vector<DocumentMarker*> markers = renderer().document().markers().markersFor(renderer().textNode());
1217    Vector<DocumentMarker*>::const_iterator markerIt = markers.begin();
1218
1219    // Give any document markers that touch this run a chance to draw before the text has been drawn.
1220    // Note end() points at the last char, not one past it like endOffset and ranges do.
1221    for ( ; markerIt != markers.end(); ++markerIt) {
1222        DocumentMarker* marker = *markerIt;
1223
1224        // Paint either the background markers or the foreground markers, but not both
1225        switch (marker->type()) {
1226            case DocumentMarker::Grammar:
1227            case DocumentMarker::Spelling:
1228            case DocumentMarker::CorrectionIndicator:
1229            case DocumentMarker::Replacement:
1230            case DocumentMarker::DictationAlternatives:
1231#if PLATFORM(IOS)
1232            // FIXME: Remove the PLATFORM(IOS)-guard.
1233            case DocumentMarker::DictationPhraseWithAlternatives:
1234#endif
1235                if (background)
1236                    continue;
1237                break;
1238            case DocumentMarker::TextMatch:
1239#if ENABLE(TELEPHONE_NUMBER_DETECTION)
1240            case DocumentMarker::TelephoneNumber:
1241#endif
1242                if (!background)
1243                    continue;
1244                break;
1245            default:
1246                continue;
1247        }
1248
1249        if (marker->endOffset() <= start())
1250            // marker is completely before this run.  This might be a marker that sits before the
1251            // first run we draw, or markers that were within runs we skipped due to truncation.
1252            continue;
1253
1254        if (marker->startOffset() > end())
1255            // marker is completely after this run, bail.  A later run will paint it.
1256            break;
1257
1258        // marker intersects this run.  Paint it.
1259        switch (marker->type()) {
1260            case DocumentMarker::Spelling:
1261            case DocumentMarker::CorrectionIndicator:
1262            case DocumentMarker::DictationAlternatives:
1263                paintDocumentMarker(pt, boxOrigin, marker, style, font, false);
1264                break;
1265            case DocumentMarker::Grammar:
1266                paintDocumentMarker(pt, boxOrigin, marker, style, font, true);
1267                break;
1268#if PLATFORM(IOS)
1269            // FIXME: See <rdar://problem/8933352>. Also, remove the PLATFORM(IOS)-guard.
1270            case DocumentMarker::DictationPhraseWithAlternatives:
1271                paintDocumentMarker(pt, boxOrigin, marker, style, font, true);
1272                break;
1273#endif
1274            case DocumentMarker::TextMatch:
1275                paintTextMatchMarker(pt, boxOrigin, marker, style, font);
1276                break;
1277            case DocumentMarker::Replacement:
1278                computeRectForReplacementMarker(marker, style, font);
1279                break;
1280#if ENABLE(TELEPHONE_NUMBER_DETECTION)
1281            case DocumentMarker::TelephoneNumber:
1282                break;
1283#endif
1284            default:
1285                ASSERT_NOT_REACHED();
1286        }
1287
1288    }
1289}
1290
1291void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const FloatPoint& boxOrigin, const CompositionUnderline& underline)
1292{
1293    if (m_truncation == cFullTruncation)
1294        return;
1295
1296    float start = 0; // start of line to draw, relative to tx
1297    float width = m_logicalWidth; // how much line to draw
1298    bool useWholeWidth = true;
1299    unsigned paintStart = m_start;
1300    unsigned paintEnd = end() + 1; // end points at the last char, not past it
1301    if (paintStart <= underline.startOffset) {
1302        paintStart = underline.startOffset;
1303        useWholeWidth = false;
1304        start = renderer().width(m_start, paintStart - m_start, textPos(), isFirstLine());
1305    }
1306    if (paintEnd != underline.endOffset) {      // end points at the last char, not past it
1307        paintEnd = std::min(paintEnd, (unsigned)underline.endOffset);
1308        useWholeWidth = false;
1309    }
1310    if (m_truncation != cNoTruncation) {
1311        paintEnd = std::min(paintEnd, (unsigned)m_start + m_truncation);
1312        useWholeWidth = false;
1313    }
1314    if (!useWholeWidth) {
1315        width = renderer().width(paintStart, paintEnd - paintStart, textPos() + start, isFirstLine());
1316    }
1317
1318    // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
1319    // All other marked text underlines are 1px thick.
1320    // If there's not enough space the underline will touch or overlap characters.
1321    int lineThickness = 1;
1322    int baseline = lineStyle().fontMetrics().ascent();
1323    if (underline.thick && logicalHeight() - baseline >= 2)
1324        lineThickness = 2;
1325
1326    // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
1327    // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
1328    start += 1;
1329    width -= 2;
1330
1331    ctx->setStrokeColor(underline.color, renderer().style().colorSpace());
1332    ctx->setStrokeThickness(lineThickness);
1333    ctx->drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, renderer().document().printing());
1334}
1335
1336int InlineTextBox::caretMinOffset() const
1337{
1338    return m_start;
1339}
1340
1341int InlineTextBox::caretMaxOffset() const
1342{
1343    return m_start + m_len;
1344}
1345
1346float InlineTextBox::textPos() const
1347{
1348    // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset
1349    // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width.
1350    if (logicalLeft() == 0)
1351        return 0;
1352    return logicalLeft() - root().logicalLeft();
1353}
1354
1355int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs) const
1356{
1357    if (isLineBreak())
1358        return 0;
1359
1360    if (lineOffset - logicalLeft() > logicalWidth())
1361        return isLeftToRightDirection() ? len() : 0;
1362    if (lineOffset - logicalLeft() < 0)
1363        return isLeftToRightDirection() ? 0 : len();
1364
1365    FontCachePurgePreventer fontCachePurgePreventer;
1366
1367    const RenderStyle& lineStyle = this->lineStyle();
1368    const Font& font = fontToUse(lineStyle, renderer());
1369    return font.offsetForPosition(constructTextRun(lineStyle, font), lineOffset - logicalLeft(), includePartialGlyphs);
1370}
1371
1372float InlineTextBox::positionForOffset(int offset) const
1373{
1374    ASSERT(offset >= m_start);
1375    ASSERT(offset <= m_start + m_len);
1376
1377    if (isLineBreak())
1378        return logicalLeft();
1379
1380    FontCachePurgePreventer fontCachePurgePreventer;
1381
1382    const RenderStyle& lineStyle = this->lineStyle();
1383    const Font& font = fontToUse(lineStyle, renderer());
1384    int from = !isLeftToRightDirection() ? offset - m_start : 0;
1385    int to = !isLeftToRightDirection() ? m_len : offset - m_start;
1386    // FIXME: Do we need to add rightBearing here?
1387    LayoutRect selectionRect = LayoutRect(logicalLeft(), 0, 0, 0);
1388    TextRun run = constructTextRun(lineStyle, font);
1389    font.adjustSelectionRectForText(run, selectionRect, from, to);
1390    return directionalPixelSnappedForPainting(selectionRect, renderer().document().deviceScaleFactor(), run.ltr()).maxX();
1391}
1392
1393TextRun InlineTextBox::constructTextRun(const RenderStyle& style, const Font& font, String* hyphenatedStringBuffer) const
1394{
1395    ASSERT(renderer().text());
1396
1397    String string = renderer().text();
1398    unsigned startPos = start();
1399    unsigned length = len();
1400
1401    if (string.length() != length || startPos)
1402        string = string.substringSharingImpl(startPos, length);
1403
1404    return constructTextRun(style, font, string, renderer().textLength() - startPos, hyphenatedStringBuffer);
1405}
1406
1407TextRun InlineTextBox::constructTextRun(const RenderStyle& style, const Font& font, String string, unsigned maximumLength, String* hyphenatedStringBuffer) const
1408{
1409    unsigned length = string.length();
1410
1411    if (hyphenatedStringBuffer) {
1412        const AtomicString& hyphenString = style.hyphenString();
1413        *hyphenatedStringBuffer = string + hyphenString;
1414        string = *hyphenatedStringBuffer;
1415        maximumLength = length + hyphenString.length();
1416    }
1417
1418    ASSERT(maximumLength >= length);
1419
1420    TextRun run(string, textPos(), expansion(), expansionBehavior(), direction(), dirOverride() || style.rtlOrdering() == VisualOrder, !renderer().canUseSimpleFontCodePath());
1421    run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
1422    if (font.primaryFont()->isSVGFont())
1423        run.setRenderingContext(SVGTextRunRenderingContext::create(renderer()));
1424
1425    // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring.
1426    run.setCharactersLength(maximumLength);
1427    ASSERT(run.charactersLength() >= run.length());
1428    return run;
1429}
1430
1431#ifndef NDEBUG
1432
1433const char* InlineTextBox::boxName() const
1434{
1435    return "InlineTextBox";
1436}
1437
1438void InlineTextBox::showBox(int printedCharacters) const
1439{
1440    String value = renderer().text();
1441    value = value.substring(start(), len());
1442    value.replaceWithLiteral('\\', "\\\\");
1443    value.replaceWithLiteral('\n', "\\n");
1444    printedCharacters += fprintf(stderr, "%s\t%p", boxName(), this);
1445    for (; printedCharacters < showTreeCharacterOffset; printedCharacters++)
1446        fputc(' ', stderr);
1447    printedCharacters = fprintf(stderr, "\t%s %p", renderer().renderName(), &renderer());
1448    const int rendererCharacterOffset = 24;
1449    for (; printedCharacters < rendererCharacterOffset; printedCharacters++)
1450        fputc(' ', stderr);
1451    fprintf(stderr, "(%d,%d) \"%s\"\n", start(), start() + len(), value.utf8().data());
1452}
1453
1454#endif
1455
1456} // namespace WebCore
1457