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 "InlineTextBox.h"
25
26#include "Chrome.h"
27#include "ChromeClient.h"
28#include "Document.h"
29#include "DocumentMarkerController.h"
30#include "Editor.h"
31#include "EllipsisBox.h"
32#include "Frame.h"
33#include "GraphicsContext.h"
34#include "HitTestResult.h"
35#include "Page.h"
36#include "PaintInfo.h"
37#include "RenderArena.h"
38#include "RenderBlock.h"
39#include "RenderCombineText.h"
40#include "RenderRubyRun.h"
41#include "RenderRubyText.h"
42#include "RenderTheme.h"
43#include "Text.h"
44#include "break_lines.h"
45#include <wtf/AlwaysInline.h>
46#include <wtf/text/CString.h>
47
48using namespace std;
49
50namespace WebCore {
51
52typedef WTF::HashMap<const InlineTextBox*, IntRect> InlineTextBoxOverflowMap;
53static InlineTextBoxOverflowMap* gTextBoxesWithOverflow;
54
55void InlineTextBox::destroy(RenderArena* arena)
56{
57    if (!m_knownToHaveNoOverflow && gTextBoxesWithOverflow)
58        gTextBoxesWithOverflow->remove(this);
59    InlineBox::destroy(arena);
60}
61
62IntRect InlineTextBox::logicalOverflowRect() const
63{
64    if (m_knownToHaveNoOverflow || !gTextBoxesWithOverflow)
65        return enclosingIntRect(logicalFrameRect());
66    return gTextBoxesWithOverflow->get(this);
67}
68
69void InlineTextBox::setLogicalOverflowRect(const IntRect& rect)
70{
71    ASSERT(!m_knownToHaveNoOverflow);
72    if (!gTextBoxesWithOverflow)
73        gTextBoxesWithOverflow = new InlineTextBoxOverflowMap;
74    gTextBoxesWithOverflow->add(this, rect);
75}
76
77int InlineTextBox::baselinePosition(FontBaseline baselineType) const
78{
79    if (!isText() || !parent())
80        return 0;
81    if (parent()->renderer() == renderer()->parent())
82        return parent()->baselinePosition(baselineType);
83    return toRenderBoxModelObject(renderer()->parent())->baselinePosition(baselineType, m_firstLine, isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
84}
85
86int InlineTextBox::lineHeight() const
87{
88    if (!isText() || !renderer()->parent())
89        return 0;
90    if (m_renderer->isBR())
91        return toRenderBR(m_renderer)->lineHeight(m_firstLine);
92    if (parent()->renderer() == renderer()->parent())
93        return parent()->lineHeight();
94    return toRenderBoxModelObject(renderer()->parent())->lineHeight(m_firstLine, isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
95}
96
97int InlineTextBox::selectionTop()
98{
99    return root()->selectionTop();
100}
101
102int InlineTextBox::selectionBottom()
103{
104    return root()->selectionBottom();
105}
106
107int InlineTextBox::selectionHeight()
108{
109    return root()->selectionHeight();
110}
111
112bool InlineTextBox::isSelected(int startPos, int endPos) const
113{
114    int sPos = max(startPos - m_start, 0);
115    int ePos = min(endPos - m_start, (int)m_len);
116    return (sPos < ePos);
117}
118
119RenderObject::SelectionState InlineTextBox::selectionState()
120{
121    RenderObject::SelectionState state = renderer()->selectionState();
122    if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
123        int startPos, endPos;
124        renderer()->selectionStartEnd(startPos, endPos);
125        // The position after a hard line break is considered to be past its end.
126        int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0);
127
128        bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
129        bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
130        if (start && end)
131            state = RenderObject::SelectionBoth;
132        else if (start)
133            state = RenderObject::SelectionStart;
134        else if (end)
135            state = RenderObject::SelectionEnd;
136        else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
137                 (state == RenderObject::SelectionStart || endPos > lastSelectable))
138            state = RenderObject::SelectionInside;
139        else if (state == RenderObject::SelectionBoth)
140            state = RenderObject::SelectionNone;
141    }
142
143    // If there are ellipsis following, make sure their selection is updated.
144    if (m_truncation != cNoTruncation && root()->ellipsisBox()) {
145        EllipsisBox* ellipsis = root()->ellipsisBox();
146        if (state != RenderObject::SelectionNone) {
147            int start, end;
148            selectionStartEnd(start, end);
149            // The ellipsis should be considered to be selected if the end of
150            // the selection is past the beginning of the truncation and the
151            // beginning of the selection is before or at the beginning of the
152            // truncation.
153            ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ?
154                RenderObject::SelectionInside : RenderObject::SelectionNone);
155        } else
156            ellipsis->setSelectionState(RenderObject::SelectionNone);
157    }
158
159    return state;
160}
161
162typedef Vector<UChar, 256> BufferForAppendingHyphen;
163
164static void adjustCharactersAndLengthForHyphen(BufferForAppendingHyphen& charactersWithHyphen, RenderStyle* style, const UChar*& characters, int& length)
165{
166    const AtomicString& hyphenString = style->hyphenString();
167    charactersWithHyphen.reserveCapacity(length + hyphenString.length());
168    charactersWithHyphen.append(characters, length);
169    charactersWithHyphen.append(hyphenString.characters(), hyphenString.length());
170    characters = charactersWithHyphen.data();
171    length += hyphenString.length();
172}
173
174IntRect InlineTextBox::selectionRect(int tx, int ty, int startPos, int endPos)
175{
176    int sPos = max(startPos - m_start, 0);
177    int ePos = min(endPos - m_start, (int)m_len);
178
179    if (sPos > ePos)
180        return IntRect();
181
182    RenderText* textObj = textRenderer();
183    int selTop = selectionTop();
184    int selHeight = selectionHeight();
185    RenderStyle* styleToUse = textObj->style(m_firstLine);
186    const Font& f = styleToUse->font();
187
188    const UChar* characters = textObj->text()->characters() + m_start;
189    int len = m_len;
190    BufferForAppendingHyphen charactersWithHyphen;
191    if (ePos == len && hasHyphen()) {
192        adjustCharactersAndLengthForHyphen(charactersWithHyphen, styleToUse, characters, len);
193        ePos = len;
194    }
195
196    IntRect r = enclosingIntRect(f.selectionRectForText(TextRun(characters, len, textObj->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride),
197                                                        IntPoint(), selHeight, sPos, ePos));
198
199    int logicalWidth = r.width();
200    if (r.x() > m_logicalWidth)
201        logicalWidth  = 0;
202    else if (r.maxX() > m_logicalWidth)
203        logicalWidth = m_logicalWidth - r.x();
204
205    IntPoint topPoint = isHorizontal() ? IntPoint(tx + m_x + r.x(), ty + selTop) : IntPoint(tx + selTop, ty + m_y + r.x());
206    int width = isHorizontal() ? logicalWidth : selHeight;
207    int height = isHorizontal() ? selHeight : logicalWidth;
208
209    return IntRect(topPoint, IntSize(width, height));
210}
211
212void InlineTextBox::deleteLine(RenderArena* arena)
213{
214    toRenderText(renderer())->removeTextBox(this);
215    destroy(arena);
216}
217
218void InlineTextBox::extractLine()
219{
220    if (m_extracted)
221        return;
222
223    toRenderText(renderer())->extractTextBox(this);
224}
225
226void InlineTextBox::attachLine()
227{
228    if (!m_extracted)
229        return;
230
231    toRenderText(renderer())->attachTextBox(this);
232}
233
234float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, bool& foundBox)
235{
236    if (foundBox) {
237        m_truncation = cFullTruncation;
238        return -1;
239    }
240
241    // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
242    float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
243
244    // Criteria for full truncation:
245    // LTR: the left edge of the ellipsis is to the left of our text run.
246    // RTL: the right edge of the ellipsis is to the right of our text run.
247    bool ltrFullTruncation = flowIsLTR && ellipsisX <= m_x;
248    bool rtlFullTruncation = !flowIsLTR && ellipsisX >= (m_x + m_logicalWidth);
249    if (ltrFullTruncation || rtlFullTruncation) {
250        // Too far.  Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
251        m_truncation = cFullTruncation;
252        foundBox = true;
253        return -1;
254    }
255
256    bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < m_x + m_logicalWidth);
257    bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > m_x);
258    if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
259        foundBox = true;
260
261        // The inline box may have different directionality than it's parent.  Since truncation
262        // behavior depends both on both the parent and the inline block's directionality, we
263        // must keep track of these separately.
264        bool ltr = isLeftToRightDirection();
265        if (ltr != flowIsLTR) {
266          // Width in pixels of the visible portion of the box, excluding the ellipsis.
267          int visibleBoxWidth = visibleRightEdge - visibleLeftEdge  - ellipsisWidth;
268          ellipsisX = ltr ? m_x + visibleBoxWidth : m_x + m_logicalWidth - visibleBoxWidth;
269        }
270
271        int offset = offsetForPosition(ellipsisX, false);
272        if (offset == 0) {
273            // No characters should be rendered.  Set ourselves to full truncation and place the ellipsis at the min of our start
274            // and the ellipsis edge.
275            m_truncation = cFullTruncation;
276            return min(ellipsisX, m_x);
277        }
278
279        // Set the truncation index on the text run.
280        m_truncation = offset;
281
282        // If we got here that means that we were only partially truncated and we need to return the pixel offset at which
283        // to place the ellipsis.
284        float widthOfVisibleText = toRenderText(renderer())->width(m_start, offset, textPos(), m_firstLine);
285
286        // The ellipsis needs to be placed just after the last visible character.
287        // Where "after" is defined by the flow directionality, not the inline
288        // box directionality.
289        // e.g. In the case of an LTR inline box truncated in an RTL flow then we can
290        // have a situation such as |Hello| -> |...He|
291        if (flowIsLTR)
292            return m_x + widthOfVisibleText;
293        else
294            return (m_x + m_logicalWidth) - widthOfVisibleText - ellipsisWidth;
295    }
296    return -1;
297}
298
299Color correctedTextColor(Color textColor, Color backgroundColor)
300{
301    // Adjust the text color if it is too close to the background color,
302    // by darkening or lightening it to move it further away.
303
304    int d = differenceSquared(textColor, backgroundColor);
305    // semi-arbitrarily chose 65025 (255^2) value here after a few tests;
306    if (d > 65025) {
307        return textColor;
308    }
309
310    int distanceFromWhite = differenceSquared(textColor, Color::white);
311    int distanceFromBlack = differenceSquared(textColor, Color::black);
312
313    if (distanceFromWhite < distanceFromBlack) {
314        return textColor.dark();
315    }
316
317    return textColor.light();
318}
319
320void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness, ColorSpace colorSpace)
321{
322    TextDrawingModeFlags mode = context->textDrawingMode();
323    if (strokeThickness > 0) {
324        TextDrawingModeFlags newMode = mode | TextModeStroke;
325        if (mode != newMode) {
326            context->setTextDrawingMode(newMode);
327            mode = newMode;
328        }
329    }
330
331    if (mode & TextModeFill && (fillColor != context->fillColor() || colorSpace != context->fillColorSpace()))
332        context->setFillColor(fillColor, colorSpace);
333
334    if (mode & TextModeStroke) {
335        if (strokeColor != context->strokeColor())
336            context->setStrokeColor(strokeColor, colorSpace);
337        if (strokeThickness != context->strokeThickness())
338            context->setStrokeThickness(strokeThickness);
339    }
340}
341
342bool InlineTextBox::isLineBreak() const
343{
344    return renderer()->isBR() || (renderer()->style()->preserveNewline() && len() == 1 && (*textRenderer()->text())[start()] == '\n');
345}
346
347bool InlineTextBox::nodeAtPoint(const HitTestRequest&, HitTestResult& result, int x, int y, int tx, int ty, int /* lineTop */, int /*lineBottom*/)
348{
349    if (isLineBreak())
350        return false;
351
352    FloatPoint boxOrigin = locationIncludingFlipping();
353    boxOrigin.move(tx, ty);
354    FloatRect rect(boxOrigin, IntSize(width(), height()));
355    if (m_truncation != cFullTruncation && visibleToHitTesting() && rect.intersects(result.rectForPoint(x, y))) {
356        renderer()->updateHitTestResult(result, flipForWritingMode(IntPoint(x - tx, y - ty)));
357        if (!result.addNodeToRectBasedTestResult(renderer()->node(), x, y, rect))
358            return true;
359    }
360    return false;
361}
362
363FloatSize InlineTextBox::applyShadowToGraphicsContext(GraphicsContext* context, const ShadowData* shadow, const FloatRect& textRect, bool stroked, bool opaque, bool horizontal)
364{
365    if (!shadow)
366        return FloatSize();
367
368    FloatSize extraOffset;
369    int shadowX = horizontal ? shadow->x() : shadow->y();
370    int shadowY = horizontal ? shadow->y() : -shadow->x();
371    FloatSize shadowOffset(shadowX, shadowY);
372    int shadowBlur = shadow->blur();
373    const Color& shadowColor = shadow->color();
374
375    if (shadow->next() || stroked || !opaque) {
376        FloatRect shadowRect(textRect);
377        shadowRect.inflate(shadowBlur);
378        shadowRect.move(shadowOffset);
379        context->save();
380        context->clip(shadowRect);
381
382        extraOffset = FloatSize(0, 2 * textRect.height() + max(0.0f, shadowOffset.height()) + shadowBlur);
383        shadowOffset -= extraOffset;
384    }
385
386    context->setShadow(shadowOffset, shadowBlur, shadowColor, context->fillColorSpace());
387    return extraOffset;
388}
389
390static void paintTextWithShadows(GraphicsContext* context, const Font& font, const TextRun& textRun, const AtomicString& emphasisMark, int emphasisMarkOffset, int startOffset, int endOffset, int truncationPoint, const FloatPoint& textOrigin,
391                                 const FloatRect& boxRect, const ShadowData* shadow, bool stroked, bool horizontal)
392{
393    Color fillColor = context->fillColor();
394    ColorSpace fillColorSpace = context->fillColorSpace();
395    bool opaque = fillColor.alpha() == 255;
396    if (!opaque)
397        context->setFillColor(Color::black, fillColorSpace);
398
399    do {
400        IntSize extraOffset;
401        if (shadow)
402            extraOffset = roundedIntSize(InlineTextBox::applyShadowToGraphicsContext(context, shadow, boxRect, stroked, opaque, horizontal));
403        else if (!opaque)
404            context->setFillColor(fillColor, fillColorSpace);
405
406        if (startOffset <= endOffset) {
407            if (emphasisMark.isEmpty())
408                context->drawText(font, textRun, textOrigin + extraOffset, startOffset, endOffset);
409            else
410                context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset), startOffset, endOffset);
411        } else {
412            if (endOffset > 0) {
413                if (emphasisMark.isEmpty())
414                    context->drawText(font, textRun, textOrigin + extraOffset,  0, endOffset);
415                else
416                    context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset),  0, endOffset);
417            } if (startOffset < truncationPoint) {
418                if (emphasisMark.isEmpty())
419                    context->drawText(font, textRun, textOrigin + extraOffset, startOffset, truncationPoint);
420                else
421                    context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset),  startOffset, truncationPoint);
422            }
423        }
424
425        if (!shadow)
426            break;
427
428        if (shadow->next() || stroked || !opaque)
429            context->restore();
430        else
431            context->clearShadow();
432
433        shadow = shadow->next();
434    } while (shadow || stroked || !opaque);
435}
436
437bool InlineTextBox::getEmphasisMarkPosition(RenderStyle* style, TextEmphasisPosition& emphasisPosition) const
438{
439    // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
440    if (style->textEmphasisMark() == TextEmphasisMarkNone)
441        return false;
442
443    emphasisPosition = style->textEmphasisPosition();
444    if (emphasisPosition == TextEmphasisPositionUnder)
445        return true; // Ruby text is always over, so it cannot suppress emphasis marks under.
446
447    RenderBlock* containingBlock = renderer()->containingBlock();
448    if (!containingBlock->isRubyBase())
449        return true; // This text is not inside a ruby base, so it does not have ruby text over it.
450
451    if (!containingBlock->parent()->isRubyRun())
452        return true; // Cannot get the ruby text.
453
454    RenderRubyText* rubyText = static_cast<RenderRubyRun*>(containingBlock->parent())->rubyText();
455
456    // The emphasis marks over are suppressed only if there is a ruby text box and it not empty.
457    return !rubyText || !rubyText->firstLineBox();
458}
459
460enum RotationDirection { Counterclockwise, Clockwise };
461
462static inline AffineTransform rotation(const FloatRect& boxRect, RotationDirection clockwise)
463{
464    return clockwise ? AffineTransform(0, 1, -1, 0, boxRect.x() + boxRect.maxY(), boxRect.maxY() - boxRect.x())
465        : AffineTransform(0, -1, 1, 0, boxRect.x() - boxRect.maxY(), boxRect.x() + boxRect.maxY());
466}
467
468void InlineTextBox::paint(PaintInfo& paintInfo, int tx, int ty, int /*lineTop*/, int /*lineBottom*/)
469{
470    if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer()->style()->visibility() != VISIBLE ||
471        m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len)
472        return;
473
474    ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
475
476    int logicalLeftSide = logicalLeftVisualOverflow();
477    int logicalRightSide = logicalRightVisualOverflow();
478    int logicalStart = logicalLeftSide + (isHorizontal() ? tx : ty);
479    int logicalExtent = logicalRightSide - logicalLeftSide;
480
481    int paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
482    int paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
483
484    if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
485        return;
486
487    bool isPrinting = textRenderer()->document()->printing();
488
489    // Determine whether or not we're selected.
490    bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
491    if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
492        // When only painting the selection, don't bother to paint if there is none.
493        return;
494
495    if (m_truncation != cNoTruncation) {
496        if (renderer()->containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) {
497            // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
498            // at which we start drawing text.
499            // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
500            // |Hello|CBA| -> |...He|CBA|
501            // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
502            // farther to the right.
503            // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
504            // truncated string i.e.  |Hello|CBA| -> |...lo|CBA|
505            int widthOfVisibleText = toRenderText(renderer())->width(m_start, m_truncation, textPos(), m_firstLine);
506            int widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
507            // FIXME: The hit testing logic also needs to take this translation int account.
508            if (isHorizontal())
509                tx += isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText;
510            else
511                ty += isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText;
512        }
513    }
514
515    GraphicsContext* context = paintInfo.context;
516
517    RenderStyle* styleToUse = renderer()->style(m_firstLine);
518
519    ty -= styleToUse->isHorizontalWritingMode() ? 0 : logicalHeight();
520
521    FloatPoint boxOrigin = locationIncludingFlipping();
522    boxOrigin.move(tx, ty);
523    FloatRect boxRect(boxOrigin, IntSize(logicalWidth(), logicalHeight()));
524
525    RenderCombineText* combinedText = styleToUse->hasTextCombine() && textRenderer()->isCombineText() && toRenderCombineText(textRenderer())->isCombined() ? toRenderCombineText(textRenderer()) : 0;
526
527    bool shouldRotate = !isHorizontal() && !combinedText;
528    if (shouldRotate)
529        context->concatCTM(rotation(boxRect, Clockwise));
530
531    // Determine whether or not we have composition underlines to draw.
532    bool containsComposition = renderer()->node() && renderer()->frame()->editor()->compositionNode() == renderer()->node();
533    bool useCustomUnderlines = containsComposition && renderer()->frame()->editor()->compositionUsesCustomUnderlines();
534
535    // Set our font.
536    const Font& font = styleToUse->font();
537
538    FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());
539
540    if (combinedText)
541        combinedText->adjustTextOrigin(textOrigin, boxRect);
542
543    // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
544    // and composition underlines.
545    if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
546#if PLATFORM(MAC)
547        // Custom highlighters go behind everything else.
548        if (styleToUse->highlight() != nullAtom && !context->paintingDisabled())
549            paintCustomHighlight(tx, ty, styleToUse->highlight());
550#endif
551
552        if (containsComposition && !useCustomUnderlines)
553            paintCompositionBackground(context, boxOrigin, styleToUse, font,
554                renderer()->frame()->editor()->compositionStart(),
555                renderer()->frame()->editor()->compositionEnd());
556
557        paintDocumentMarkers(context, boxOrigin, styleToUse, font, true);
558
559        if (haveSelection && !useCustomUnderlines)
560            paintSelection(context, boxOrigin, styleToUse, font);
561    }
562
563    // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
564    Color textFillColor;
565    Color textStrokeColor;
566    Color emphasisMarkColor;
567    float textStrokeWidth = styleToUse->textStrokeWidth();
568    const ShadowData* textShadow = paintInfo.forceBlackText ? 0 : styleToUse->textShadow();
569
570    if (paintInfo.forceBlackText) {
571        textFillColor = Color::black;
572        textStrokeColor = Color::black;
573        emphasisMarkColor = Color::black;
574    } else {
575        textFillColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextFillColor);
576
577        // Make the text fill color legible against a white background
578        if (styleToUse->forceBackgroundsToWhite())
579            textFillColor = correctedTextColor(textFillColor, Color::white);
580
581        textStrokeColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
582
583        // Make the text stroke color legible against a white background
584        if (styleToUse->forceBackgroundsToWhite())
585            textStrokeColor = correctedTextColor(textStrokeColor, Color::white);
586
587        emphasisMarkColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextEmphasisColor);
588
589        // Make the text stroke color legible against a white background
590        if (styleToUse->forceBackgroundsToWhite())
591            emphasisMarkColor = correctedTextColor(emphasisMarkColor, Color::white);
592    }
593
594    bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection);
595    bool paintSelectedTextSeparately = false;
596
597    Color selectionFillColor = textFillColor;
598    Color selectionStrokeColor = textStrokeColor;
599    Color selectionEmphasisMarkColor = emphasisMarkColor;
600    float selectionStrokeWidth = textStrokeWidth;
601    const ShadowData* selectionShadow = textShadow;
602    if (haveSelection) {
603        // Check foreground color first.
604        Color foreground = paintInfo.forceBlackText ? Color::black : renderer()->selectionForegroundColor();
605        if (foreground.isValid() && foreground != selectionFillColor) {
606            if (!paintSelectedTextOnly)
607                paintSelectedTextSeparately = true;
608            selectionFillColor = foreground;
609        }
610
611        Color emphasisMarkForeground = paintInfo.forceBlackText ? Color::black : renderer()->selectionEmphasisMarkColor();
612        if (emphasisMarkForeground.isValid() && emphasisMarkForeground != selectionEmphasisMarkColor) {
613            if (!paintSelectedTextOnly)
614                paintSelectedTextSeparately = true;
615            selectionEmphasisMarkColor = emphasisMarkForeground;
616        }
617
618        if (RenderStyle* pseudoStyle = renderer()->getCachedPseudoStyle(SELECTION)) {
619            const ShadowData* shadow = paintInfo.forceBlackText ? 0 : pseudoStyle->textShadow();
620            if (shadow != selectionShadow) {
621                if (!paintSelectedTextOnly)
622                    paintSelectedTextSeparately = true;
623                selectionShadow = shadow;
624            }
625
626            float strokeWidth = pseudoStyle->textStrokeWidth();
627            if (strokeWidth != selectionStrokeWidth) {
628                if (!paintSelectedTextOnly)
629                    paintSelectedTextSeparately = true;
630                selectionStrokeWidth = strokeWidth;
631            }
632
633            Color stroke = paintInfo.forceBlackText ? Color::black : pseudoStyle->visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
634            if (stroke != selectionStrokeColor) {
635                if (!paintSelectedTextOnly)
636                    paintSelectedTextSeparately = true;
637                selectionStrokeColor = stroke;
638            }
639        }
640    }
641
642    int length = m_len;
643    const UChar* characters;
644    if (!combinedText)
645        characters = textRenderer()->text()->characters() + m_start;
646    else
647        combinedText->charactersToRender(m_start, characters, length);
648
649    BufferForAppendingHyphen charactersWithHyphen;
650    if (hasHyphen())
651        adjustCharactersAndLengthForHyphen(charactersWithHyphen, styleToUse, characters, length);
652
653    TextRun textRun(characters, length, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride || styleToUse->visuallyOrdered());
654
655    int sPos = 0;
656    int ePos = 0;
657    if (paintSelectedTextOnly || paintSelectedTextSeparately)
658        selectionStartEnd(sPos, ePos);
659
660    if (m_truncation != cNoTruncation) {
661        sPos = min<int>(sPos, m_truncation);
662        ePos = min<int>(ePos, m_truncation);
663        length = m_truncation;
664    }
665
666    int emphasisMarkOffset = 0;
667    TextEmphasisPosition emphasisMarkPosition;
668    bool hasTextEmphasis = getEmphasisMarkPosition(styleToUse, emphasisMarkPosition);
669    const AtomicString& emphasisMark = hasTextEmphasis ? styleToUse->textEmphasisMarkString() : nullAtom;
670    if (!emphasisMark.isEmpty())
671        emphasisMarkOffset = emphasisMarkPosition == TextEmphasisPositionOver ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
672
673    if (!paintSelectedTextOnly) {
674        // For stroked painting, we have to change the text drawing mode.  It's probably dangerous to leave that mutated as a side
675        // effect, so only when we know we're stroking, do a save/restore.
676        if (textStrokeWidth > 0)
677            context->save();
678
679        updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
680        if (!paintSelectedTextSeparately || ePos <= sPos) {
681            // FIXME: Truncate right-to-left text correctly.
682            paintTextWithShadows(context, font, textRun, nullAtom, 0, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
683        } else
684            paintTextWithShadows(context, font, textRun, nullAtom, 0, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
685
686        if (!emphasisMark.isEmpty()) {
687            updateGraphicsContext(context, emphasisMarkColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
688
689            static TextRun objectReplacementCharacterTextRun(&objectReplacementCharacter, 1);
690            TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
691            FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
692            if (combinedText)
693                context->concatCTM(rotation(boxRect, Clockwise));
694
695            if (!paintSelectedTextSeparately || ePos <= sPos) {
696                // FIXME: Truncate right-to-left text correctly.
697                paintTextWithShadows(context, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, 0, length, length, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
698            } else
699                paintTextWithShadows(context, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, ePos, sPos, length, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
700
701            if (combinedText)
702                context->concatCTM(rotation(boxRect, Counterclockwise));
703        }
704
705        if (textStrokeWidth > 0)
706            context->restore();
707    }
708
709    if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) {
710        // paint only the text that is selected
711        if (selectionStrokeWidth > 0)
712            context->save();
713
714        updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth, styleToUse->colorSpace());
715        paintTextWithShadows(context, font, textRun, nullAtom, 0, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
716        if (!emphasisMark.isEmpty()) {
717            updateGraphicsContext(context, selectionEmphasisMarkColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
718
719            static TextRun objectReplacementCharacterTextRun(&objectReplacementCharacter, 1);
720            TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
721            FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
722            if (combinedText)
723                context->concatCTM(rotation(boxRect, Clockwise));
724
725            paintTextWithShadows(context, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, sPos, ePos, length, emphasisMarkTextOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
726
727            if (combinedText)
728                context->concatCTM(rotation(boxRect, Counterclockwise));
729        }
730        if (selectionStrokeWidth > 0)
731            context->restore();
732    }
733
734    // Paint decorations
735    int textDecorations = styleToUse->textDecorationsInEffect();
736    if (textDecorations != TDNONE && paintInfo.phase != PaintPhaseSelection) {
737        updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
738        paintDecoration(context, boxOrigin, textDecorations, textShadow);
739    }
740
741    if (paintInfo.phase == PaintPhaseForeground) {
742        paintDocumentMarkers(context, boxOrigin, styleToUse, font, false);
743
744        if (useCustomUnderlines) {
745            const Vector<CompositionUnderline>& underlines = renderer()->frame()->editor()->customCompositionUnderlines();
746            size_t numUnderlines = underlines.size();
747
748            for (size_t index = 0; index < numUnderlines; ++index) {
749                const CompositionUnderline& underline = underlines[index];
750
751                if (underline.endOffset <= start())
752                    // underline is completely before this run.  This might be an underline that sits
753                    // before the first run we draw, or underlines that were within runs we skipped
754                    // due to truncation.
755                    continue;
756
757                if (underline.startOffset <= end()) {
758                    // underline intersects this run.  Paint it.
759                    paintCompositionUnderline(context, boxOrigin, underline);
760                    if (underline.endOffset > end() + 1)
761                        // underline also runs into the next run. Bail now, no more marker advancement.
762                        break;
763                } else
764                    // underline is completely after this run, bail.  A later run will paint it.
765                    break;
766            }
767        }
768    }
769
770    if (shouldRotate)
771        context->concatCTM(rotation(boxRect, Counterclockwise));
772}
773
774void InlineTextBox::selectionStartEnd(int& sPos, int& ePos)
775{
776    int startPos, endPos;
777    if (renderer()->selectionState() == RenderObject::SelectionInside) {
778        startPos = 0;
779        endPos = textRenderer()->textLength();
780    } else {
781        textRenderer()->selectionStartEnd(startPos, endPos);
782        if (renderer()->selectionState() == RenderObject::SelectionStart)
783            endPos = textRenderer()->textLength();
784        else if (renderer()->selectionState() == RenderObject::SelectionEnd)
785            startPos = 0;
786    }
787
788    sPos = max(startPos - m_start, 0);
789    ePos = min(endPos - m_start, (int)m_len);
790}
791
792void InlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font)
793{
794    // See if we have a selection to paint at all.
795    int sPos, ePos;
796    selectionStartEnd(sPos, ePos);
797    if (sPos >= ePos)
798        return;
799
800    Color textColor = style->visitedDependentColor(CSSPropertyColor);
801    Color c = renderer()->selectionBackgroundColor();
802    if (!c.isValid() || c.alpha() == 0)
803        return;
804
805    // If the text color ends up being the same as the selection background, invert the selection
806    // background.  This should basically never happen, since the selection has transparency.
807    if (textColor == c)
808        c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
809
810    context->save();
811    updateGraphicsContext(context, c, c, 0, style->colorSpace());  // Don't draw text at all!
812
813    // If the text is truncated, let the thing being painted in the truncation
814    // draw its own highlight.
815    int length = m_truncation != cNoTruncation ? m_truncation : m_len;
816    const UChar* characters = textRenderer()->text()->characters() + m_start;
817
818    BufferForAppendingHyphen charactersWithHyphen;
819    if (ePos == length && hasHyphen()) {
820        adjustCharactersAndLengthForHyphen(charactersWithHyphen, style, characters, length);
821        ePos = length;
822    }
823
824    int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
825    int selHeight = selectionHeight();
826    FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
827    context->clip(FloatRect(localOrigin, FloatSize(m_logicalWidth, selHeight)));
828    context->drawHighlightForText(font, TextRun(characters, length, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(),
829                                  !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()),
830                                  localOrigin, selHeight, c, style->colorSpace(), sPos, ePos);
831    context->restore();
832}
833
834void InlineTextBox::paintCompositionBackground(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, int startPos, int endPos)
835{
836    int offset = m_start;
837    int sPos = max(startPos - offset, 0);
838    int ePos = min(endPos - offset, (int)m_len);
839
840    if (sPos >= ePos)
841        return;
842
843    context->save();
844
845    Color c = Color(225, 221, 85);
846
847    updateGraphicsContext(context, c, c, 0, style->colorSpace()); // Don't draw text at all!
848
849    int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
850    int selHeight = selectionHeight();
851    FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
852    context->drawHighlightForText(font, TextRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(),
853                                  !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()),
854                                  localOrigin, selHeight, c, style->colorSpace(), sPos, ePos);
855    context->restore();
856}
857
858#if PLATFORM(MAC)
859
860void InlineTextBox::paintCustomHighlight(int tx, int ty, const AtomicString& type)
861{
862    Frame* frame = renderer()->frame();
863    if (!frame)
864        return;
865    Page* page = frame->page();
866    if (!page)
867        return;
868
869    RootInlineBox* r = root();
870    FloatRect rootRect(tx + r->x(), ty + selectionTop(), r->logicalWidth(), selectionHeight());
871    FloatRect textRect(tx + x(), rootRect.y(), logicalWidth(), rootRect.height());
872
873    page->chrome()->client()->paintCustomHighlight(renderer()->node(), type, textRect, rootRect, true, false);
874}
875
876#endif
877
878void InlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& boxOrigin, int deco, const ShadowData* shadow)
879{
880    if (m_truncation == cFullTruncation)
881        return;
882
883    FloatPoint localOrigin = boxOrigin;
884
885    float width = m_logicalWidth;
886    if (m_truncation != cNoTruncation) {
887        width = toRenderText(renderer())->width(m_start, m_truncation, textPos(), m_firstLine);
888        if (!isLeftToRightDirection())
889            localOrigin.move(m_logicalWidth - width, 0);
890    }
891
892    // Get the text decoration colors.
893    Color underline, overline, linethrough;
894    renderer()->getTextDecorationColors(deco, underline, overline, linethrough, true);
895
896    // Use a special function for underlines to get the positioning exactly right.
897    bool isPrinting = textRenderer()->document()->printing();
898    context->setStrokeThickness(1.0f); // FIXME: We should improve this rule and not always just assume 1.
899
900    bool linesAreOpaque = !isPrinting && (!(deco & UNDERLINE) || underline.alpha() == 255) && (!(deco & OVERLINE) || overline.alpha() == 255) && (!(deco & LINE_THROUGH) || linethrough.alpha() == 255);
901
902    RenderStyle* styleToUse = renderer()->style(m_firstLine);
903    int baseline = styleToUse->fontMetrics().ascent();
904
905    bool setClip = false;
906    int extraOffset = 0;
907    if (!linesAreOpaque && shadow && shadow->next()) {
908        context->save();
909        FloatRect clipRect(localOrigin, FloatSize(width, baseline + 2));
910        for (const ShadowData* s = shadow; s; s = s->next()) {
911            FloatRect shadowRect(localOrigin, FloatSize(width, baseline + 2));
912            shadowRect.inflate(s->blur());
913            int shadowX = isHorizontal() ? s->x() : s->y();
914            int shadowY = isHorizontal() ? s->y() : -s->x();
915            shadowRect.move(shadowX, shadowY);
916            clipRect.unite(shadowRect);
917            extraOffset = max(extraOffset, max(0, shadowY) + s->blur());
918        }
919        context->save();
920        context->clip(clipRect);
921        extraOffset += baseline + 2;
922        localOrigin.move(0, extraOffset);
923        setClip = true;
924    }
925
926    ColorSpace colorSpace = renderer()->style()->colorSpace();
927    bool setShadow = false;
928
929    do {
930        if (shadow) {
931            if (!shadow->next()) {
932                // The last set of lines paints normally inside the clip.
933                localOrigin.move(0, -extraOffset);
934                extraOffset = 0;
935            }
936            int shadowX = isHorizontal() ? shadow->x() : shadow->y();
937            int shadowY = isHorizontal() ? shadow->y() : -shadow->x();
938            context->setShadow(FloatSize(shadowX, shadowY - extraOffset), shadow->blur(), shadow->color(), colorSpace);
939            setShadow = true;
940            shadow = shadow->next();
941        }
942
943        if (deco & UNDERLINE) {
944            context->setStrokeColor(underline, colorSpace);
945            context->setStrokeStyle(SolidStroke);
946            // Leave one pixel of white between the baseline and the underline.
947            context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + baseline + 1), width, isPrinting);
948        }
949        if (deco & OVERLINE) {
950            context->setStrokeColor(overline, colorSpace);
951            context->setStrokeStyle(SolidStroke);
952            context->drawLineForText(localOrigin, width, isPrinting);
953        }
954        if (deco & LINE_THROUGH) {
955            context->setStrokeColor(linethrough, colorSpace);
956            context->setStrokeStyle(SolidStroke);
957            context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + 2 * baseline / 3), width, isPrinting);
958        }
959    } while (shadow);
960
961    if (setClip)
962        context->restore();
963    else if (setShadow)
964        context->clearShadow();
965}
966
967static GraphicsContext::TextCheckingLineStyle textCheckingLineStyleForMarkerType(DocumentMarker::MarkerType markerType)
968{
969    switch (markerType) {
970    case DocumentMarker::Spelling:
971        return GraphicsContext::TextCheckingSpellingLineStyle;
972    case DocumentMarker::Grammar:
973        return GraphicsContext::TextCheckingGrammarLineStyle;
974    case DocumentMarker::CorrectionIndicator:
975        return GraphicsContext::TextCheckingReplacementLineStyle;
976    default:
977        ASSERT_NOT_REACHED();
978        return GraphicsContext::TextCheckingSpellingLineStyle;
979    }
980}
981
982void InlineTextBox::paintSpellingOrGrammarMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, const DocumentMarker& marker, RenderStyle* style, const Font& font, bool grammar)
983{
984    // Never print spelling/grammar markers (5327887)
985    if (textRenderer()->document()->printing())
986        return;
987
988    if (m_truncation == cFullTruncation)
989        return;
990
991    float start = 0; // start of line to draw, relative to tx
992    float width = m_logicalWidth; // how much line to draw
993
994    // Determine whether we need to measure text
995    bool markerSpansWholeBox = true;
996    if (m_start <= (int)marker.startOffset)
997        markerSpansWholeBox = false;
998    if ((end() + 1) != marker.endOffset)      // end points at the last char, not past it
999        markerSpansWholeBox = false;
1000    if (m_truncation != cNoTruncation)
1001        markerSpansWholeBox = false;
1002
1003    if (!markerSpansWholeBox || grammar) {
1004        int startPosition = max<int>(marker.startOffset - m_start, 0);
1005        int endPosition = min<int>(marker.endOffset - m_start, m_len);
1006
1007        if (m_truncation != cNoTruncation)
1008            endPosition = min<int>(endPosition, m_truncation);
1009
1010        // Calculate start & width
1011        int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1012        int selHeight = selectionHeight();
1013        FloatPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY);
1014        TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered());
1015
1016        // FIXME: Convert the document markers to float rects.
1017        IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, selHeight, startPosition, endPosition));
1018        start = markerRect.x() - startPoint.x();
1019        width = markerRect.width();
1020
1021        // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to
1022        // display a toolTip. We don't do this for misspelling markers.
1023        if (grammar) {
1024            markerRect.move(-boxOrigin.x(), -boxOrigin.y());
1025            markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1026            renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect);
1027        }
1028    }
1029
1030    // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to
1031    // make sure to fit within those bounds.  This means the top pixel(s) of the underline will overlap the
1032    // bottom pixel(s) of the glyphs in smaller font sizes.  The alternatives are to increase the line spacing (bad!!)
1033    // or decrease the underline thickness.  The overlap is actually the most useful, and matches what AppKit does.
1034    // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so
1035    // we pin to two pixels under the baseline.
1036    int lineThickness = cMisspellingLineThickness;
1037    int baseline = renderer()->style(m_firstLine)->fontMetrics().ascent();
1038    int descent = logicalHeight() - baseline;
1039    int underlineOffset;
1040    if (descent <= (2 + lineThickness)) {
1041        // Place the underline at the very bottom of the text in small/medium fonts.
1042        underlineOffset = logicalHeight() - lineThickness;
1043    } else {
1044        // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
1045        underlineOffset = baseline + 2;
1046    }
1047    pt->drawLineForTextChecking(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, textCheckingLineStyleForMarkerType(marker.type));
1048}
1049
1050void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, const DocumentMarker& marker, RenderStyle* style, const Font& font)
1051{
1052    // Use same y positioning and height as for selection, so that when the selection and this highlight are on
1053    // the same word there are no pieces sticking out.
1054    int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1055    int selHeight = selectionHeight();
1056
1057    int sPos = max(marker.startOffset - m_start, (unsigned)0);
1058    int ePos = min(marker.endOffset - m_start, (unsigned)m_len);
1059    TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered());
1060
1061    // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates.
1062    IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, IntPoint(m_x, selectionTop()), selHeight, sPos, ePos));
1063    markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1064    renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect);
1065
1066    // Optionally highlight the text
1067    if (renderer()->frame()->editor()->markedTextMatchesAreHighlighted()) {
1068        Color color = marker.activeMatch ?
1069            renderer()->theme()->platformActiveTextSearchHighlightColor() :
1070            renderer()->theme()->platformInactiveTextSearchHighlightColor();
1071        pt->save();
1072        updateGraphicsContext(pt, color, color, 0, style->colorSpace());  // Don't draw text at all!
1073#if PLATFORM(ANDROID)
1074        pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, style->colorSpace(), sPos, ePos, marker.activeMatch);
1075#else
1076        pt->clip(FloatRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selHeight));
1077        pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, style->colorSpace(), sPos, ePos);
1078#endif
1079        pt->restore();
1080    }
1081}
1082
1083void InlineTextBox::computeRectForReplacementMarker(const DocumentMarker& marker, RenderStyle* style, const Font& font)
1084{
1085    // Replacement markers are not actually drawn, but their rects need to be computed for hit testing.
1086    int y = selectionTop();
1087    int h = selectionHeight();
1088
1089    int sPos = max(marker.startOffset - m_start, (unsigned)0);
1090    int ePos = min(marker.endOffset - m_start, (unsigned)m_len);
1091    TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered());
1092    IntPoint startPoint = IntPoint(m_x, y);
1093
1094    // Compute and store the rect associated with this marker.
1095    IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, h, sPos, ePos));
1096    markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1097    renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect);
1098}
1099
1100void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, bool background)
1101{
1102    if (!renderer()->node())
1103        return;
1104
1105    Vector<DocumentMarker> markers = renderer()->document()->markers()->markersForNode(renderer()->node());
1106    Vector<DocumentMarker>::iterator markerIt = markers.begin();
1107
1108    // Give any document markers that touch this run a chance to draw before the text has been drawn.
1109    // Note end() points at the last char, not one past it like endOffset and ranges do.
1110    for ( ; markerIt != markers.end(); markerIt++) {
1111        const DocumentMarker& marker = *markerIt;
1112
1113        // Paint either the background markers or the foreground markers, but not both
1114        switch (marker.type) {
1115            case DocumentMarker::Grammar:
1116            case DocumentMarker::Spelling:
1117            case DocumentMarker::CorrectionIndicator:
1118            case DocumentMarker::Replacement:
1119                if (background)
1120                    continue;
1121                break;
1122            case DocumentMarker::TextMatch:
1123                if (!background)
1124                    continue;
1125                break;
1126            default:
1127                continue;
1128        }
1129
1130        if (marker.endOffset <= start())
1131            // marker is completely before this run.  This might be a marker that sits before the
1132            // first run we draw, or markers that were within runs we skipped due to truncation.
1133            continue;
1134
1135        if (marker.startOffset > end())
1136            // marker is completely after this run, bail.  A later run will paint it.
1137            break;
1138
1139        // marker intersects this run.  Paint it.
1140        switch (marker.type) {
1141            case DocumentMarker::Spelling:
1142                paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, false);
1143                break;
1144            case DocumentMarker::Grammar:
1145                paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, true);
1146                break;
1147            case DocumentMarker::TextMatch:
1148                paintTextMatchMarker(pt, boxOrigin, marker, style, font);
1149                break;
1150            case DocumentMarker::CorrectionIndicator:
1151                paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, false);
1152                break;
1153            case DocumentMarker::Replacement:
1154                computeRectForReplacementMarker(marker, style, font);
1155                break;
1156            default:
1157                ASSERT_NOT_REACHED();
1158        }
1159
1160    }
1161}
1162
1163void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const FloatPoint& boxOrigin, const CompositionUnderline& underline)
1164{
1165    if (m_truncation == cFullTruncation)
1166        return;
1167
1168    float start = 0; // start of line to draw, relative to tx
1169    float width = m_logicalWidth; // how much line to draw
1170    bool useWholeWidth = true;
1171    unsigned paintStart = m_start;
1172    unsigned paintEnd = end() + 1; // end points at the last char, not past it
1173    if (paintStart <= underline.startOffset) {
1174        paintStart = underline.startOffset;
1175        useWholeWidth = false;
1176        start = toRenderText(renderer())->width(m_start, paintStart - m_start, textPos(), m_firstLine);
1177    }
1178    if (paintEnd != underline.endOffset) {      // end points at the last char, not past it
1179        paintEnd = min(paintEnd, (unsigned)underline.endOffset);
1180        useWholeWidth = false;
1181    }
1182    if (m_truncation != cNoTruncation) {
1183        paintEnd = min(paintEnd, (unsigned)m_start + m_truncation);
1184        useWholeWidth = false;
1185    }
1186    if (!useWholeWidth) {
1187        width = toRenderText(renderer())->width(paintStart, paintEnd - paintStart, textPos() + start, m_firstLine);
1188    }
1189
1190    // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
1191    // All other marked text underlines are 1px thick.
1192    // If there's not enough space the underline will touch or overlap characters.
1193    int lineThickness = 1;
1194    int baseline = renderer()->style(m_firstLine)->fontMetrics().ascent();
1195    if (underline.thick && logicalHeight() - baseline >= 2)
1196        lineThickness = 2;
1197
1198    // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
1199    // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
1200    start += 1;
1201    width -= 2;
1202
1203    ctx->setStrokeColor(underline.color, renderer()->style()->colorSpace());
1204    ctx->setStrokeThickness(lineThickness);
1205    ctx->drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, textRenderer()->document()->printing());
1206}
1207
1208int InlineTextBox::caretMinOffset() const
1209{
1210    return m_start;
1211}
1212
1213int InlineTextBox::caretMaxOffset() const
1214{
1215    return m_start + m_len;
1216}
1217
1218unsigned InlineTextBox::caretMaxRenderedOffset() const
1219{
1220    return m_start + m_len;
1221}
1222
1223float InlineTextBox::textPos() const
1224{
1225    // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset
1226    // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width.
1227    if (logicalLeft() == 0)
1228        return 0;
1229    return logicalLeft() - root()->logicalLeft();
1230}
1231
1232int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs) const
1233{
1234    if (isLineBreak())
1235        return 0;
1236
1237    int leftOffset = isLeftToRightDirection() ? 0 : m_len;
1238    int rightOffset = isLeftToRightDirection() ? m_len : 0;
1239    bool blockIsInOppositeDirection = renderer()->containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection();
1240    if (blockIsInOppositeDirection)
1241        swap(leftOffset, rightOffset);
1242
1243    if (lineOffset - logicalLeft() > logicalWidth())
1244        return rightOffset;
1245    if (lineOffset - logicalLeft() < 0)
1246        return leftOffset;
1247
1248    RenderText* text = toRenderText(renderer());
1249    RenderStyle* style = text->style(m_firstLine);
1250    const Font* f = &style->font();
1251    int offset = f->offsetForPosition(TextRun(textRenderer()->text()->characters() + m_start, m_len,
1252        textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()),
1253        lineOffset - logicalLeft(), includePartialGlyphs);
1254    if (blockIsInOppositeDirection && (!offset || offset == m_len))
1255        return !offset ? m_len : 0;
1256    return offset;
1257}
1258
1259float InlineTextBox::positionForOffset(int offset) const
1260{
1261    ASSERT(offset >= m_start);
1262    ASSERT(offset <= m_start + m_len);
1263
1264    if (isLineBreak())
1265        return logicalLeft();
1266
1267    RenderText* text = toRenderText(renderer());
1268    const Font& f = text->style(m_firstLine)->font();
1269    int from = !isLeftToRightDirection() ? offset - m_start : 0;
1270    int to = !isLeftToRightDirection() ? m_len : offset - m_start;
1271    // FIXME: Do we need to add rightBearing here?
1272    return f.selectionRectForText(TextRun(text->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride),
1273                                  IntPoint(logicalLeft(), 0), 0, from, to).maxX();
1274}
1275
1276bool InlineTextBox::containsCaretOffset(int offset) const
1277{
1278    // Offsets before the box are never "in".
1279    if (offset < m_start)
1280        return false;
1281
1282    int pastEnd = m_start + m_len;
1283
1284    // Offsets inside the box (not at either edge) are always "in".
1285    if (offset < pastEnd)
1286        return true;
1287
1288    // Offsets outside the box are always "out".
1289    if (offset > pastEnd)
1290        return false;
1291
1292    // Offsets at the end are "out" for line breaks (they are on the next line).
1293    if (isLineBreak())
1294        return false;
1295
1296    // Offsets at the end are "in" for normal boxes (but the caller has to check affinity).
1297    return true;
1298}
1299
1300} // namespace WebCore
1301