InlineTextBox.cpp 69.8 KB
Newer Older
darin's avatar
darin committed
1
/*
eseidel's avatar
eseidel committed
2 3
 * (C) 1999 Lars Knoll (knoll@kde.org)
 * (C) 2000 Dirk Mueller (mueller@kde.org)
4
 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
eseidel's avatar
eseidel committed
5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
ddkilzer's avatar
ddkilzer committed
18 19
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
eseidel's avatar
eseidel committed
20 21 22 23 24 25
 *
 */

#include "config.h"
#include "InlineTextBox.h"

26
#include "Chrome.h"
darin@apple.com's avatar
darin@apple.com committed
27
#include "ChromeClient.h"
darin's avatar
darin committed
28
#include "Document.h"
29
#include "DocumentMarkerController.h"
darin's avatar
darin committed
30
#include "Editor.h"
31
#include "EllipsisBox.h"
32
#include "FontCache.h"
33
#include "Frame.h"
darin's avatar
darin committed
34
#include "GraphicsContext.h"
bdakin's avatar
bdakin committed
35
#include "HitTestResult.h"
darin@apple.com's avatar
darin@apple.com committed
36
#include "Page.h"
37
#include "PaintInfo.h"
38
#include "RenderedDocumentMarker.h"
darin's avatar
darin committed
39
#include "RenderArena.h"
40
#include "RenderBR.h"
darin's avatar
darin committed
41
#include "RenderBlock.h"
42
#include "RenderCombineText.h"
43 44
#include "RenderRubyRun.h"
#include "RenderRubyText.h"
aroben@apple.com's avatar
aroben@apple.com committed
45
#include "RenderTheme.h"
46
#include "Settings.h"
47
#include "SVGTextRunRenderingContext.h"
darin's avatar
darin committed
48
#include "Text.h"
49
#include "WebCoreMemoryInstrumentation.h"
darin's avatar
darin committed
50
#include "break_lines.h"
mjs's avatar
mjs committed
51
#include <wtf/AlwaysInline.h>
52
#include <wtf/text/CString.h>
mjs's avatar
mjs committed
53

darin's avatar
darin committed
54 55
using namespace std;

darin's avatar
darin committed
56
namespace WebCore {
eseidel's avatar
eseidel committed
57

58
typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap;
59 60 61 62
static InlineTextBoxOverflowMap* gTextBoxesWithOverflow;

void InlineTextBox::destroy(RenderArena* arena)
{
63
    if (!knownToHaveNoOverflow() && gTextBoxesWithOverflow)
64 65 66 67
        gTextBoxesWithOverflow->remove(this);
    InlineBox::destroy(arena);
}

68 69 70 71 72 73 74 75 76
void InlineTextBox::markDirty(bool dirty)
{
    if (dirty) {
        m_len = 0;
        m_start = 0;
    }
    InlineBox::markDirty(dirty);
}

77
LayoutRect InlineTextBox::logicalOverflowRect() const
78
{
79
    if (knownToHaveNoOverflow() || !gTextBoxesWithOverflow)
80 81 82 83
        return enclosingIntRect(logicalFrameRect());
    return gTextBoxesWithOverflow->get(this);
}

84
void InlineTextBox::setLogicalOverflowRect(const LayoutRect& rect)
85
{
86
    ASSERT(!knownToHaveNoOverflow());
87 88 89 90 91
    if (!gTextBoxesWithOverflow)
        gTextBoxesWithOverflow = new InlineTextBoxOverflowMap;
    gTextBoxesWithOverflow->add(this, rect);
}

92
int InlineTextBox::baselinePosition(FontBaseline baselineType) const
93 94 95
{
    if (!isText() || !parent())
        return 0;
96 97
    if (parent()->renderer() == renderer()->parent())
        return parent()->baselinePosition(baselineType);
98
    return toRenderBoxModelObject(renderer()->parent())->baselinePosition(baselineType, isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
99
}
100 101

LayoutUnit InlineTextBox::lineHeight() const
102
{
103
    if (!isText() || !renderer()->parent())
104 105
        return 0;
    if (m_renderer->isBR())
106
        return toRenderBR(m_renderer)->lineHeight(isFirstLineStyle());
107 108
    if (parent()->renderer() == renderer()->parent())
        return parent()->lineHeight();
109
    return toRenderBoxModelObject(renderer()->parent())->lineHeight(isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
110 111
}

112
LayoutUnit InlineTextBox::selectionTop()
rwlbuis's avatar
rwlbuis committed
113 114 115 116
{
    return root()->selectionTop();
}

117
LayoutUnit InlineTextBox::selectionBottom()
118 119 120 121
{
    return root()->selectionBottom();
}

122
LayoutUnit InlineTextBox::selectionHeight()
rwlbuis's avatar
rwlbuis committed
123 124 125 126
{
    return root()->selectionHeight();
}

eseidel's avatar
eseidel committed
127 128
bool InlineTextBox::isSelected(int startPos, int endPos) const
{
129 130
    LayoutUnit sPos = max<LayoutUnit>(startPos - m_start, 0);
    LayoutUnit ePos = min<LayoutUnit>(endPos - m_start, m_len);
eseidel's avatar
eseidel committed
131 132 133 134 135
    return (sPos < ePos);
}

RenderObject::SelectionState InlineTextBox::selectionState()
{
136
    RenderObject::SelectionState state = renderer()->selectionState();
137
    if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
eseidel's avatar
eseidel committed
138
        int startPos, endPos;
139
        renderer()->selectionStartEnd(startPos, endPos);
adele's avatar
adele committed
140 141
        // The position after a hard line break is considered to be past its end.
        int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0);
142

eseidel's avatar
eseidel committed
143
        bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
adele's avatar
adele committed
144
        bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
eseidel's avatar
eseidel committed
145 146 147 148 149 150 151
        if (start && end)
            state = RenderObject::SelectionBoth;
        else if (start)
            state = RenderObject::SelectionStart;
        else if (end)
            state = RenderObject::SelectionEnd;
        else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
adele's avatar
adele committed
152
                 (state == RenderObject::SelectionStart || endPos > lastSelectable))
eseidel's avatar
eseidel committed
153
            state = RenderObject::SelectionInside;
adele's avatar
adele committed
154 155
        else if (state == RenderObject::SelectionBoth)
            state = RenderObject::SelectionNone;
eseidel's avatar
eseidel committed
156
    }
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173

    // If there are ellipsis following, make sure their selection is updated.
    if (m_truncation != cNoTruncation && root()->ellipsisBox()) {
        EllipsisBox* ellipsis = root()->ellipsisBox();
        if (state != RenderObject::SelectionNone) {
            int start, end;
            selectionStartEnd(start, end);
            // The ellipsis should be considered to be selected if the end of
            // the selection is past the beginning of the truncation and the
            // beginning of the selection is before or at the beginning of the
            // truncation.
            ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ?
                RenderObject::SelectionInside : RenderObject::SelectionNone);
        } else
            ellipsis->setSelectionState(RenderObject::SelectionNone);
    }

eseidel's avatar
eseidel committed
174 175 176
    return state;
}

177
static void adjustCharactersAndLengthForHyphen(BufferForAppendingHyphen& charactersWithHyphen, RenderStyle* style, String& string, int& length)
178 179 180
{
    const AtomicString& hyphenString = style->hyphenString();
    charactersWithHyphen.reserveCapacity(length + hyphenString.length());
181
    charactersWithHyphen.append(string);
182
    charactersWithHyphen.append(hyphenString);
183
    string = charactersWithHyphen.toString();
184 185 186
    length += hyphenString.length();
}

187
LayoutRect InlineTextBox::localSelectionRect(int startPos, int endPos)
eseidel's avatar
eseidel committed
188
{
darin's avatar
darin committed
189 190
    int sPos = max(startPos - m_start, 0);
    int ePos = min(endPos - m_start, (int)m_len);
eseidel's avatar
eseidel committed
191
    
192
    if (sPos > ePos)
193
        return LayoutRect();
eseidel's avatar
eseidel committed
194

195 196
    FontCachePurgePreventer fontCachePurgePreventer;

197
    RenderText* textObj = textRenderer();
198 199
    LayoutUnit selTop = selectionTop();
    LayoutUnit selHeight = selectionHeight();
200
    RenderStyle* styleToUse = textObj->style(isFirstLineStyle());
201
    const Font& font = styleToUse->font();
202 203

    BufferForAppendingHyphen charactersWithHyphen;
204
    bool respectHyphen = ePos == m_len && hasHyphen();
205
    TextRun textRun = constructTextRun(styleToUse, font, respectHyphen ? &charactersWithHyphen : 0);
206 207
    if (respectHyphen)
        endPos = textRun.length();
eseidel's avatar
eseidel committed
208

209 210 211 212 213 214
    FloatPoint startingPoint = FloatPoint(logicalLeft(), selTop);
    LayoutRect r;
    if (sPos || ePos != static_cast<int>(m_len))
        r = enclosingIntRect(font.selectionRectForText(textRun, startingPoint, selHeight, sPos, ePos));
    else // Avoid computing the font width when the entire line box is selected as an optimization.
        r = enclosingIntRect(FloatRect(startingPoint, FloatSize(m_logicalWidth, selHeight)));
215

216
    LayoutUnit logicalWidth = r.width();
217
    if (r.x() > logicalRight())
218
        logicalWidth  = 0;
219 220 221
    else if (r.maxX() > logicalRight())
        logicalWidth = logicalRight() - r.x();

222 223 224
    LayoutPoint topPoint = isHorizontal() ? LayoutPoint(r.x(), selTop) : LayoutPoint(selTop, r.x());
    LayoutUnit width = isHorizontal() ? logicalWidth : selHeight;
    LayoutUnit height = isHorizontal() ? selHeight : logicalWidth;
225

226
    return LayoutRect(topPoint, LayoutSize(width, height));
eseidel's avatar
eseidel committed
227 228 229 230
}

void InlineTextBox::deleteLine(RenderArena* arena)
{
231
    toRenderText(renderer())->removeTextBox(this);
eseidel's avatar
eseidel committed
232 233 234 235 236
    destroy(arena);
}

void InlineTextBox::extractLine()
{
237
    if (extracted())
eseidel's avatar
eseidel committed
238 239
        return;

240
    toRenderText(renderer())->extractTextBox(this);
eseidel's avatar
eseidel committed
241 242 243 244
}

void InlineTextBox::attachLine()
{
245
    if (!extracted())
eseidel's avatar
eseidel committed
246 247
        return;
    
248
    toRenderText(renderer())->attachTextBox(this);
eseidel's avatar
eseidel committed
249 250
}

251
float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, float &truncatedWidth, bool& foundBox)
eseidel's avatar
eseidel committed
252 253 254 255 256 257
{
    if (foundBox) {
        m_truncation = cFullTruncation;
        return -1;
    }

dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
258
    // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
259
    float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
eseidel's avatar
eseidel committed
260
    
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
261 262 263
    // Criteria for full truncation:
    // LTR: the left edge of the ellipsis is to the left of our text run.
    // RTL: the right edge of the ellipsis is to the right of our text run.
264 265
    bool ltrFullTruncation = flowIsLTR && ellipsisX <= left();
    bool rtlFullTruncation = !flowIsLTR && ellipsisX >= left() + logicalWidth();
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
266 267 268 269 270 271
    if (ltrFullTruncation || rtlFullTruncation) {
        // Too far.  Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
        m_truncation = cFullTruncation;
        foundBox = true;
        return -1;
    }
eseidel's avatar
eseidel committed
272

273 274
    bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < right());
    bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > left());
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
275 276
    if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
        foundBox = true;
eseidel's avatar
eseidel committed
277

dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
278 279 280
        // The inline box may have different directionality than it's parent.  Since truncation
        // behavior depends both on both the parent and the inline block's directionality, we
        // must keep track of these separately.
281
        bool ltr = isLeftToRightDirection();
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
282 283 284
        if (ltr != flowIsLTR) {
          // Width in pixels of the visible portion of the box, excluding the ellipsis.
          int visibleBoxWidth = visibleRightEdge - visibleLeftEdge  - ellipsisWidth;
285
          ellipsisX = ltr ? left() + visibleBoxWidth : right() - visibleBoxWidth;
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
286 287
        }

dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
288 289 290 291 292
        int offset = offsetForPosition(ellipsisX, false);
        if (offset == 0) {
            // No characters should be rendered.  Set ourselves to full truncation and place the ellipsis at the min of our start
            // and the ellipsis edge.
            m_truncation = cFullTruncation;
293
            truncatedWidth += ellipsisWidth;
294
            return min(ellipsisX, x());
eseidel's avatar
eseidel committed
295
        }
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
296

dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
297
        // Set the truncation index on the text run.
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
298
        m_truncation = offset;
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
299 300 301

        // If we got here that means that we were only partially truncated and we need to return the pixel offset at which
        // to place the ellipsis.
302
        float widthOfVisibleText = toRenderText(renderer())->width(m_start, offset, textPos(), isFirstLineStyle());
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
303 304 305 306 307 308

        // The ellipsis needs to be placed just after the last visible character.
        // Where "after" is defined by the flow directionality, not the inline
        // box directionality.
        // e.g. In the case of an LTR inline box truncated in an RTL flow then we can
        // have a situation such as |Hello| -> |...He|
309
        truncatedWidth += widthOfVisibleText + ellipsisWidth;
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
310
        if (flowIsLTR)
311
            return left() + widthOfVisibleText;
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
312
        else
313
            return right() - widthOfVisibleText - ellipsisWidth;
eseidel's avatar
eseidel committed
314
    }
315
    truncatedWidth += logicalWidth();
eseidel's avatar
eseidel committed
316 317 318
    return -1;
}

oliver's avatar
oliver committed
319
Color correctedTextColor(Color textColor, Color backgroundColor) 
eseidel's avatar
eseidel committed
320 321 322 323
{
    // Adjust the text color if it is too close to the background color,
    // by darkening or lightening it to move it further away.
    
adele's avatar
adele committed
324 325 326
    int d = differenceSquared(textColor, backgroundColor);
    // semi-arbitrarily chose 65025 (255^2) value here after a few tests; 
    if (d > 65025) {
eseidel's avatar
eseidel committed
327 328 329
        return textColor;
    }
    
adele's avatar
adele committed
330 331
    int distanceFromWhite = differenceSquared(textColor, Color::white);
    int distanceFromBlack = differenceSquared(textColor, Color::black);
eseidel's avatar
eseidel committed
332 333 334 335 336 337 338 339

    if (distanceFromWhite < distanceFromBlack) {
        return textColor.dark();
    }
    
    return textColor.light();
}

340
void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness, ColorSpace colorSpace)
341
{
342
    TextDrawingModeFlags mode = context->textDrawingMode();
343
    if (strokeThickness > 0) {
344
        TextDrawingModeFlags newMode = mode | TextModeStroke;
345 346 347 348 349 350
        if (mode != newMode) {
            context->setTextDrawingMode(newMode);
            mode = newMode;
        }
    }
    
351
    if (mode & TextModeFill && (fillColor != context->fillColor() || colorSpace != context->fillColorSpace()))
352
        context->setFillColor(fillColor, colorSpace);
353

354
    if (mode & TextModeStroke) {
355
        if (strokeColor != context->strokeColor())
356
            context->setStrokeColor(strokeColor, colorSpace);
357 358 359
        if (strokeThickness != context->strokeThickness())
            context->setStrokeThickness(strokeThickness);
    }
360 361
}

362 363
bool InlineTextBox::isLineBreak() const
{
364
    return renderer()->isBR() || (renderer()->style()->preserveNewline() && len() == 1 && (*textRenderer()->text())[start()] == '\n');
365 366
}

367
bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/)
eseidel's avatar
eseidel committed
368
{
369
    if (isLineBreak())
eseidel's avatar
eseidel committed
370 371
        return false;

372
    FloatPoint boxOrigin = locationIncludingFlipping();
373
    boxOrigin.moveBy(accumulatedOffset);
374
    FloatRect rect(boxOrigin, size());
375 376
    if (m_truncation != cFullTruncation && visibleToHitTesting() && locationInContainer.intersects(rect)) {
        renderer()->updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset)));
377
        if (!result.addNodeToRectBasedTestResult(renderer()->node(), request, locationInContainer, rect))
378
            return true;
eseidel's avatar
eseidel committed
379 380 381 382
    }
    return false;
}

383
FloatSize InlineTextBox::applyShadowToGraphicsContext(GraphicsContext* context, const ShadowData* shadow, const FloatRect& textRect, bool stroked, bool opaque, bool horizontal)
384 385 386 387 388
{
    if (!shadow)
        return FloatSize();

    FloatSize extraOffset;
389 390
    int shadowX = horizontal ? shadow->x() : shadow->y();
    int shadowY = horizontal ? shadow->y() : -shadow->x();
391
    FloatSize shadowOffset(shadowX, shadowY);
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
    int shadowBlur = shadow->blur();
    const Color& shadowColor = shadow->color();

    if (shadow->next() || stroked || !opaque) {
        FloatRect shadowRect(textRect);
        shadowRect.inflate(shadowBlur);
        shadowRect.move(shadowOffset);
        context->save();
        context->clip(shadowRect);

        extraOffset = FloatSize(0, 2 * textRect.height() + max(0.0f, shadowOffset.height()) + shadowBlur);
        shadowOffset -= extraOffset;
    }

    context->setShadow(shadowOffset, shadowBlur, shadowColor, context->fillColorSpace());
    return extraOffset;
}

410 411
static void paintTextWithShadows(GraphicsContext* context, const Font& font, const TextRun& textRun, const AtomicString& emphasisMark, int emphasisMarkOffset, int startOffset, int endOffset, int truncationPoint, const FloatPoint& textOrigin,
                                 const FloatRect& boxRect, const ShadowData* shadow, bool stroked, bool horizontal)
mitz@apple.com's avatar
mitz@apple.com committed
412
{
mitz@apple.com's avatar
mitz@apple.com committed
413
    Color fillColor = context->fillColor();
414
    ColorSpace fillColorSpace = context->fillColorSpace();
mitz@apple.com's avatar
mitz@apple.com committed
415 416
    bool opaque = fillColor.alpha() == 255;
    if (!opaque)
417
        context->setFillColor(Color::black, fillColorSpace);
mitz@apple.com's avatar
mitz@apple.com committed
418

mitz@apple.com's avatar
mitz@apple.com committed
419
    do {
mitz@apple.com's avatar
mitz@apple.com committed
420
        IntSize extraOffset;
421
        if (shadow)
422
            extraOffset = roundedIntSize(InlineTextBox::applyShadowToGraphicsContext(context, shadow, boxRect, stroked, opaque, horizontal));
423
        else if (!opaque)
424
            context->setFillColor(fillColor, fillColorSpace);
mitz@apple.com's avatar
mitz@apple.com committed
425

426 427 428 429 430 431 432 433 434 435 436
        if (startOffset <= endOffset) {
            if (emphasisMark.isEmpty())
                context->drawText(font, textRun, textOrigin + extraOffset, startOffset, endOffset);
            else
                context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset), startOffset, endOffset);
        } else {
            if (endOffset > 0) {
                if (emphasisMark.isEmpty())
                    context->drawText(font, textRun, textOrigin + extraOffset,  0, endOffset);
                else
                    context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset),  0, endOffset);
437 438
            }
            if (startOffset < truncationPoint) {
439 440 441 442 443
                if (emphasisMark.isEmpty())
                    context->drawText(font, textRun, textOrigin + extraOffset, startOffset, truncationPoint);
                else
                    context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset),  startOffset, truncationPoint);
            }
mitz@apple.com's avatar
mitz@apple.com committed
444 445 446 447 448
        }

        if (!shadow)
            break;

449
        if (shadow->next() || stroked || !opaque)
mitz@apple.com's avatar
mitz@apple.com committed
450 451 452 453
            context->restore();
        else
            context->clearShadow();

454
        shadow = shadow->next();
mitz@apple.com's avatar
mitz@apple.com committed
455
    } while (shadow || stroked || !opaque);
mitz@apple.com's avatar
mitz@apple.com committed
456 457
}

458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
bool InlineTextBox::getEmphasisMarkPosition(RenderStyle* style, TextEmphasisPosition& emphasisPosition) const
{
    // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
    if (style->textEmphasisMark() == TextEmphasisMarkNone)
        return false;

    emphasisPosition = style->textEmphasisPosition();
    if (emphasisPosition == TextEmphasisPositionUnder)
        return true; // Ruby text is always over, so it cannot suppress emphasis marks under.

    RenderBlock* containingBlock = renderer()->containingBlock();
    if (!containingBlock->isRubyBase())
        return true; // This text is not inside a ruby base, so it does not have ruby text over it.

    if (!containingBlock->parent()->isRubyRun())
        return true; // Cannot get the ruby text.

475
    RenderRubyText* rubyText = toRenderRubyRun(containingBlock->parent())->rubyText();
476 477 478 479 480

    // The emphasis marks over are suppressed only if there is a ruby text box and it not empty.
    return !rubyText || !rubyText->firstLineBox();
}

481 482 483 484 485 486 487 488
enum RotationDirection { Counterclockwise, Clockwise };

static inline AffineTransform rotation(const FloatRect& boxRect, RotationDirection clockwise)
{
    return clockwise ? AffineTransform(0, 1, -1, 0, boxRect.x() + boxRect.maxY(), boxRect.maxY() - boxRect.x())
        : AffineTransform(0, -1, 1, 0, boxRect.x() - boxRect.maxY(), boxRect.x() + boxRect.maxY());
}

489
void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/)
eseidel's avatar
eseidel committed
490
{
491
    if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer()->style()->visibility() != VISIBLE ||
492
        m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len)
eseidel's avatar
eseidel committed
493
        return;
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
494

weinig's avatar
weinig committed
495
    ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
eseidel's avatar
eseidel committed
496

497 498 499 500
    LayoutUnit logicalLeftSide = logicalLeftVisualOverflow();
    LayoutUnit logicalRightSide = logicalRightVisualOverflow();
    LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y());
    LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide;
501
    
502 503
    LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
    LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
504
    
505
    LayoutPoint adjustedPaintOffset = roundedIntPoint(paintOffset);
506
    
507
    if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
eseidel's avatar
eseidel committed
508
        return;
darin's avatar
darin committed
509

510
    bool isPrinting = textRenderer()->document()->printing();
511
    
eseidel's avatar
eseidel committed
512
    // Determine whether or not we're selected.
513
    bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
weinig's avatar
weinig committed
514
    if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
eseidel's avatar
eseidel committed
515 516 517
        // When only painting the selection, don't bother to paint if there is none.
        return;

dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
518
    if (m_truncation != cNoTruncation) {
519
        if (renderer()->containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) {
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
520 521 522 523 524 525 526 527
            // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
            // at which we start drawing text.
            // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
            // |Hello|CBA| -> |...He|CBA|
            // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
            // farther to the right.
            // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
            // truncated string i.e.  |Hello|CBA| -> |...lo|CBA|
528
            LayoutUnit widthOfVisibleText = toRenderText(renderer())->width(m_start, m_truncation, textPos(), isFirstLineStyle());
529 530 531
            LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
            // FIXME: The hit testing logic also needs to take this translation into account.
            LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0);
532
            adjustedPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
533 534 535
        }
    }

mitz@apple.com's avatar
mitz@apple.com committed
536 537
    GraphicsContext* context = paintInfo.context;

538
    RenderStyle* styleToUse = renderer()->style(isFirstLineStyle());
539
    
540
    adjustedPaintOffset.move(0, styleToUse->isHorizontalWritingMode() ? 0 : -logicalHeight());
541

542
    FloatPoint boxOrigin = locationIncludingFlipping();
543
    boxOrigin.move(adjustedPaintOffset.x(), adjustedPaintOffset.y());
544
    FloatRect boxRect(boxOrigin, LayoutSize(logicalWidth(), logicalHeight()));
545

546
    RenderCombineText* combinedText = styleToUse->hasTextCombine() && textRenderer()->isCombineText() && toRenderCombineText(textRenderer())->isCombined() ? toRenderCombineText(textRenderer()) : 0;
547 548 549 550 551

    bool shouldRotate = !isHorizontal() && !combinedText;
    if (shouldRotate)
        context->concatCTM(rotation(boxRect, Clockwise));

darin's avatar
darin committed
552
    // Determine whether or not we have composition underlines to draw.
553 554
    bool containsComposition = renderer()->node() && renderer()->frame()->editor()->compositionNode() == renderer()->node();
    bool useCustomUnderlines = containsComposition && renderer()->frame()->editor()->compositionUsesCustomUnderlines();
eseidel's avatar
eseidel committed
555

556
    // Determine the text colors and selection colors.
557 558
    Color textFillColor;
    Color textStrokeColor;
559
    Color emphasisMarkColor;
560
    float textStrokeWidth = styleToUse->textStrokeWidth();
561
    const ShadowData* textShadow = paintInfo.forceBlackText() ? 0 : styleToUse->textShadow();
weinig's avatar
weinig committed
562

563
    if (paintInfo.forceBlackText()) {
sullivan's avatar
sullivan committed
564 565
        textFillColor = Color::black;
        textStrokeColor = Color::black;
566
        emphasisMarkColor = Color::black;
567
    } else {
568 569
        textFillColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextFillColor);
        
570 571 572 573 574 575 576 577
        bool forceBackgroundToWhite = false;
        if (isPrinting) {
            if (styleToUse->printColorAdjust() == PrintColorAdjustEconomy)
                forceBackgroundToWhite = true;
            if (textRenderer()->document()->settings() && textRenderer()->document()->settings()->shouldPrintBackgrounds())
                forceBackgroundToWhite = false;
        }

578
        // Make the text fill color legible against a white background
579
        if (forceBackgroundToWhite)
580
            textFillColor = correctedTextColor(textFillColor, Color::white);
mitz@apple.com's avatar
mitz@apple.com committed
581

582 583
        textStrokeColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
        
584
        // Make the text stroke color legible against a white background
585
        if (forceBackgroundToWhite)
586
            textStrokeColor = correctedTextColor(textStrokeColor, Color::white);
587 588 589 590

        emphasisMarkColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextEmphasisColor);
        
        // Make the text stroke color legible against a white background
591
        if (forceBackgroundToWhite)
592
            emphasisMarkColor = correctedTextColor(emphasisMarkColor, Color::white);
eseidel's avatar
eseidel committed
593 594
    }

weinig's avatar
weinig committed
595
    bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection);
mitz@apple.com's avatar
mitz@apple.com committed
596 597
    bool paintSelectedTextSeparately = false;

598 599
    Color selectionFillColor = textFillColor;
    Color selectionStrokeColor = textStrokeColor;
600
    Color selectionEmphasisMarkColor = emphasisMarkColor;
601
    float selectionStrokeWidth = textStrokeWidth;
602
    const ShadowData* selectionShadow = textShadow;
eseidel's avatar
eseidel committed
603
    if (haveSelection) {
604
        // Check foreground color first.
605
        Color foreground = paintInfo.forceBlackText() ? Color::black : renderer()->selectionForegroundColor();
606
        if (foreground.isValid() && foreground != selectionFillColor) {
607 608
            if (!paintSelectedTextOnly)
                paintSelectedTextSeparately = true;
609
            selectionFillColor = foreground;
610
        }
mitz@apple.com's avatar
mitz@apple.com committed
611

612
        Color emphasisMarkForeground = paintInfo.forceBlackText() ? Color::black : renderer()->selectionEmphasisMarkColor();
613 614 615 616 617 618
        if (emphasisMarkForeground.isValid() && emphasisMarkForeground != selectionEmphasisMarkColor) {
            if (!paintSelectedTextOnly)
                paintSelectedTextSeparately = true;
            selectionEmphasisMarkColor = emphasisMarkForeground;
        }

619
        if (RenderStyle* pseudoStyle = renderer()->getCachedPseudoStyle(SELECTION)) {
620
            const ShadowData* shadow = paintInfo.forceBlackText() ? 0 : pseudoStyle->textShadow();
mitz@apple.com's avatar
mitz@apple.com committed
621
            if (shadow != selectionShadow) {
622 623
                if (!paintSelectedTextOnly)
                    paintSelectedTextSeparately = true;
mitz@apple.com's avatar
mitz@apple.com committed
624
                selectionShadow = shadow;
625
            }
mitz@apple.com's avatar
mitz@apple.com committed
626

hyatt's avatar
hyatt committed
627
            float strokeWidth = pseudoStyle->textStrokeWidth();
628 629 630 631 632 633
            if (strokeWidth != selectionStrokeWidth) {
                if (!paintSelectedTextOnly)
                    paintSelectedTextSeparately = true;
                selectionStrokeWidth = strokeWidth;
            }

634
            Color stroke = paintInfo.forceBlackText() ? Color::black : pseudoStyle->visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
635 636 637 638 639
            if (stroke != selectionStrokeColor) {
                if (!paintSelectedTextOnly)
                    paintSelectedTextSeparately = true;
                selectionStrokeColor = stroke;
            }
eseidel's avatar
eseidel committed
640 641 642
        }
    }

643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
    // Set our font.
    const Font& font = styleToUse->font();

    FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());

    if (combinedText)
        combinedText->adjustTextOrigin(textOrigin, boxRect);

    // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
    // and composition underlines.
    if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
#if PLATFORM(MAC)
        // Custom highlighters go behind everything else.
        if (styleToUse->highlight() != nullAtom && !context->paintingDisabled())
            paintCustomHighlight(adjustedPaintOffset, styleToUse->highlight());
#endif

        if (containsComposition && !useCustomUnderlines)
            paintCompositionBackground(context, boxOrigin, styleToUse, font,
                renderer()->frame()->editor()->compositionStart(),
                renderer()->frame()->editor()->compositionEnd());

        paintDocumentMarkers(context, boxOrigin, styleToUse, font, true);

        if (haveSelection && !useCustomUnderlines)
            paintSelection(context, boxOrigin, styleToUse, font, selectionFillColor);
    }

    if (Frame* frame = renderer()->frame()) {
        if (Page* page = frame->page()) {
            // FIXME: Right now, InlineTextBoxes never call addRelevantUnpaintedObject() even though they might
            // legitimately be unpainted if they are waiting on a slow-loading web font. We should fix that, and
            // when we do, we will have to account for the fact the InlineTextBoxes do not always have unique
            // renderers and Page currently relies on each unpainted object having a unique renderer.
            if (paintInfo.phase == PaintPhaseForeground)
                page->addRelevantRepaintedObject(renderer(), IntRect(boxOrigin.x(), boxOrigin.y(), logicalWidth(), logicalHeight()));
        }
    }

    // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
683
    int length = m_len;
684
    int maximumLength;
685
    String string;
686
    if (!combinedText) {
687 688
        string = textRenderer()->text();
        if (static_cast<unsigned>(length) != string.length() || m_start) {
689
            ASSERT_WITH_SECURITY_IMPLICATION(static_cast<unsigned>(m_start + length) <= string.length());
690 691
            string = string.substringSharingImpl(m_start, length);
        }
692 693
        maximumLength = textRenderer()->textLength() - m_start;
    } else {
694
        combinedText->getStringToRender(m_start, string, length);
695 696
        maximumLength = length;
    }
697

698
    BufferForAppendingHyphen charactersWithHyphen;
699
    TextRun textRun = constructTextRun(styleToUse, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0);
700
    if (hasHyphen())
701
        length = textRun.length();
mitz@apple.com's avatar
mitz@apple.com committed
702

703 704
    int sPos = 0;
    int ePos = 0;
mitz@apple.com's avatar
mitz@apple.com committed
705
    if (paintSelectedTextOnly || paintSelectedTextSeparately)
eseidel's avatar
eseidel committed
706
        selectionStartEnd(sPos, ePos);
weinig's avatar
weinig committed
707

708 709 710 711 712 713
    if (m_truncation != cNoTruncation) {
        sPos = min<int>(sPos, m_truncation);
        ePos = min<int>(ePos, m_truncation);
        length = m_truncation;
    }

714
    int emphasisMarkOffset = 0;
715 716 717 718
    TextEmphasisPosition emphasisMarkPosition;
    bool hasTextEmphasis = getEmphasisMarkPosition(styleToUse, emphasisMarkPosition);
    const AtomicString& emphasisMark = hasTextEmphasis ? styleToUse->textEmphasisMarkString() : nullAtom;
    if (!emphasisMark.isEmpty())
719
        emphasisMarkOffset = emphasisMarkPosition == TextEmphasisPositionOver ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
720

mitz@apple.com's avatar
mitz@apple.com committed
721 722 723
    if (!paintSelectedTextOnly) {
        // For stroked painting, we have to change the text drawing mode.  It's probably dangerous to leave that mutated as a side
        // effect, so only when we know we're stroking, do a save/restore.
724
        GraphicsContextStateSaver stateSaver(*context, textStrokeWidth > 0);
mitz@apple.com's avatar
mitz@apple.com committed
725

726
        updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
727
        if (!paintSelectedTextSeparately || ePos <= sPos) {
mitz@apple.com's avatar
mitz@apple.com committed
728
            // FIXME: Truncate right-to-left text correctly.
729
            paintTextWithShadows(context, font, textRun, nullAtom, 0, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
mitz@apple.com's avatar
mitz@apple.com committed
730
        } else
731 732 733 734
            paintTextWithShadows(context, font, textRun, nullAtom, 0, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());

        if (!emphasisMark.isEmpty()) {
            updateGraphicsContext(context, emphasisMarkColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
735

736
            DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
737 738 739 740 741
            TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
            FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
            if (combinedText)
                context->concatCTM(rotation(boxRect, Clockwise));

742 743
            if (!paintSelectedTextSeparately || ePos <= sPos) {
                // FIXME: Truncate right-to-left text correctly.
744
                paintTextWithShadows(context, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, 0, length, length, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
745
            } else
746 747 748 749
                paintTextWithShadows(context, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, ePos, sPos, length, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());

            if (combinedText)
                context->concatCTM(rotation(boxRect, Counterclockwise));
750
        }
mitz@apple.com's avatar
mitz@apple.com committed
751 752 753 754
    }

    if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) {
        // paint only the text that is selected
755
        GraphicsContextStateSaver stateSaver(*context, selectionStrokeWidth > 0);
mitz@apple.com's avatar
mitz@apple.com committed
756

757
        updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth, styleToUse->colorSpace());
758 759 760
        paintTextWithShadows(context, font, textRun, nullAtom, 0, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
        if (!emphasisMark.isEmpty()) {
            updateGraphicsContext(context, selectionEmphasisMarkColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
761

762
            DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
763 764 765 766 767 768 769 770 771
            TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
            FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
            if (combinedText)
                context->concatCTM(rotation(boxRect, Clockwise));

            paintTextWithShadows(context, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, sPos, ePos, length, emphasisMarkTextOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());

            if (combinedText)
                context->concatCTM(rotation(boxRect, Counterclockwise));
772
        }
eseidel's avatar
eseidel committed
773 774 775
    }

    // Paint decorations
776
    ETextDecoration textDecorations = styleToUse->textDecorationsInEffect();
777
    if (textDecorations != TDNONE && paintInfo.phase != PaintPhaseSelection) {
778
        updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
779 780
        if (combinedText)
            context->concatCTM(rotation(boxRect, Clockwise));
781
        paintDecoration(context, boxOrigin, textDecorations, styleToUse->textDecorationStyle(), textShadow);
782 783
        if (combinedText)
            context->concatCTM(rotation(boxRect, Counterclockwise));
eseidel's avatar
eseidel committed
784 785
    }

786
    if (paintInfo.phase == PaintPhaseForeground) {
787
        paintDocumentMarkers(context, boxOrigin, styleToUse, font, false);
eseidel's avatar
eseidel committed
788

darin's avatar
darin committed
789
        if (useCustomUnderlines) {
790
            const Vector<CompositionUnderline>& underlines = renderer()->frame()->editor()->customCompositionUnderlines();
darin's avatar
darin committed
791
            size_t numUnderlines = underlines.size();
eseidel's avatar
eseidel committed
792

darin's avatar
darin committed
793 794 795 796 797 798 799 800 801 802 803
            for (size_t index = 0; index < numUnderlines; ++index) {
                const CompositionUnderline& underline = underlines[index];

                if (underline.endOffset <= start())
                    // underline is completely before this run.  This might be an underline that sits
                    // before the first run we draw, or underlines that were within runs we skipped 
                    // due to truncation.
                    continue;
                
                if (underline.startOffset <= end()) {
                    // underline intersects this run.  Paint it.
804
                    paintCompositionUnderline(context, boxOrigin, underline);
darin's avatar
darin committed
805 806 807 808 809
                    if (underline.endOffset > end() + 1)