InlineTextBox.cpp 69.7 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"
darin's avatar
darin committed
40
#include "RenderBlock.h"
41
#include "RenderCombineText.h"
42
#include "RenderLineBreak.h"
43 44
#include "RenderRubyRun.h"
#include "RenderRubyText.h"
aroben@apple.com's avatar
aroben@apple.com committed
45
#include "RenderTheme.h"
46
#include "RenderView.h"
47
#include "Settings.h"
48
#include "SVGTextRunRenderingContext.h"
darin's avatar
darin committed
49
#include "Text.h"
darin's avatar
darin committed
50
#include "break_lines.h"
51
#include <wtf/text/CString.h>
mjs's avatar
mjs committed
52

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

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

57 58 59 60 61 62 63 64
struct SameSizeAsInlineTextBox : public InlineBox {
    unsigned variables[1];
    unsigned short variables2[2];
    void* pointers[2];
};

COMPILE_ASSERT(sizeof(InlineTextBox) == sizeof(SameSizeAsInlineTextBox), InlineTextBox_should_stay_small);

65
typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap;
66 67
static InlineTextBoxOverflowMap* gTextBoxesWithOverflow;

68
void InlineTextBox::destroy(RenderArena& arena)
69
{
70
    if (!knownToHaveNoOverflow() && gTextBoxesWithOverflow)
71 72 73 74
        gTextBoxesWithOverflow->remove(this);
    InlineBox::destroy(arena);
}

75 76 77 78 79 80 81 82 83
void InlineTextBox::markDirty(bool dirty)
{
    if (dirty) {
        m_len = 0;
        m_start = 0;
    }
    InlineBox::markDirty(dirty);
}

84
LayoutRect InlineTextBox::logicalOverflowRect() const
85
{
86
    if (knownToHaveNoOverflow() || !gTextBoxesWithOverflow)
87 88 89 90
        return enclosingIntRect(logicalFrameRect());
    return gTextBoxesWithOverflow->get(this);
}

91
void InlineTextBox::setLogicalOverflowRect(const LayoutRect& rect)
92
{
93
    ASSERT(!knownToHaveNoOverflow());
94 95 96 97 98
    if (!gTextBoxesWithOverflow)
        gTextBoxesWithOverflow = new InlineTextBoxOverflowMap;
    gTextBoxesWithOverflow->add(this, rect);
}

99
int InlineTextBox::baselinePosition(FontBaseline baselineType) const
100
{
antti@apple.com's avatar
antti@apple.com committed
101
    if (!behavesLikeText() || !parent())
102
        return 0;
103
    if (&parent()->renderer() == renderer().parent())
104
        return parent()->baselinePosition(baselineType);
105
    return toRenderBoxModelObject(renderer().parent())->baselinePosition(baselineType, isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
106
}
107 108

LayoutUnit InlineTextBox::lineHeight() const
109
{
antti@apple.com's avatar
antti@apple.com committed
110
    if (!behavesLikeText() || !renderer().parent())
111
        return 0;
112
    if (&parent()->renderer() == renderer().parent())
113
        return parent()->lineHeight();
114
    return toRenderBoxModelObject(renderer().parent())->lineHeight(isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
115 116
}

117
LayoutUnit InlineTextBox::selectionTop()
rwlbuis's avatar
rwlbuis committed
118
{
119
    return root().selectionTop();
rwlbuis's avatar
rwlbuis committed
120 121
}

122
LayoutUnit InlineTextBox::selectionBottom()
123
{
124
    return root().selectionBottom();
125 126
}

127
LayoutUnit InlineTextBox::selectionHeight()
rwlbuis's avatar
rwlbuis committed
128
{
129
    return root().selectionHeight();
rwlbuis's avatar
rwlbuis committed
130 131
}

eseidel's avatar
eseidel committed
132 133
bool InlineTextBox::isSelected(int startPos, int endPos) const
{
134 135
    LayoutUnit sPos = max<LayoutUnit>(startPos - m_start, 0);
    LayoutUnit ePos = min<LayoutUnit>(endPos - m_start, m_len);
eseidel's avatar
eseidel committed
136 137 138 139 140
    return (sPos < ePos);
}

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

eseidel's avatar
eseidel committed
148
        bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
adele's avatar
adele committed
149
        bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
eseidel's avatar
eseidel committed
150 151 152 153 154 155 156
        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
157
                 (state == RenderObject::SelectionStart || endPos > lastSelectable))
eseidel's avatar
eseidel committed
158
            state = RenderObject::SelectionInside;
adele's avatar
adele committed
159 160
        else if (state == RenderObject::SelectionBoth)
            state = RenderObject::SelectionNone;
eseidel's avatar
eseidel committed
161
    }
162 163

    // If there are ellipsis following, make sure their selection is updated.
164 165
    if (m_truncation != cNoTruncation && root().ellipsisBox()) {
        EllipsisBox* ellipsis = root().ellipsisBox();
166 167 168 169 170 171 172 173 174 175 176 177 178
        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
179 180 181
    return state;
}

182
static void adjustCharactersAndLengthForHyphen(BufferForAppendingHyphen& charactersWithHyphen, const RenderStyle& style, String& string, int& length)
183
{
184
    const AtomicString& hyphenString = style.hyphenString();
185
    charactersWithHyphen.reserveCapacity(length + hyphenString.length());
186
    charactersWithHyphen.append(string);
187
    charactersWithHyphen.append(hyphenString);
188
    string = charactersWithHyphen.toString();
189 190 191
    length += hyphenString.length();
}

192 193 194 195 196 197 198 199 200 201
static const Font& fontToUse(const RenderStyle& style, const RenderText& renderer)
{
    if (style.hasTextCombine() && renderer.isCombineText()) {
        const RenderCombineText& textCombineRenderer = toRenderCombineText(renderer);
        if (textCombineRenderer.isCombined())
            return textCombineRenderer.textCombineFont();
    }
    return style.font();
}

202
LayoutRect InlineTextBox::localSelectionRect(int startPos, int endPos)
eseidel's avatar
eseidel committed
203
{
darin's avatar
darin committed
204 205
    int sPos = max(startPos - m_start, 0);
    int ePos = min(endPos - m_start, (int)m_len);
eseidel's avatar
eseidel committed
206
    
207
    if (sPos > ePos)
208
        return LayoutRect();
eseidel's avatar
eseidel committed
209

210 211
    FontCachePurgePreventer fontCachePurgePreventer;

212 213
    LayoutUnit selTop = selectionTop();
    LayoutUnit selHeight = selectionHeight();
214
    const RenderStyle& lineStyle = this->lineStyle();
215
    const Font& font = fontToUse(lineStyle, renderer());
216 217

    BufferForAppendingHyphen charactersWithHyphen;
218
    bool respectHyphen = ePos == m_len && hasHyphen();
219
    TextRun textRun = constructTextRun(lineStyle, font, respectHyphen ? &charactersWithHyphen : 0);
220 221
    if (respectHyphen)
        endPos = textRun.length();
eseidel's avatar
eseidel committed
222

223 224 225 226 227 228
    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)));
229

230
    LayoutUnit logicalWidth = r.width();
231
    if (r.x() > logicalRight())
232
        logicalWidth  = 0;
233 234 235
    else if (r.maxX() > logicalRight())
        logicalWidth = logicalRight() - r.x();

236 237 238
    LayoutPoint topPoint = isHorizontal() ? LayoutPoint(r.x(), selTop) : LayoutPoint(selTop, r.x());
    LayoutUnit width = isHorizontal() ? logicalWidth : selHeight;
    LayoutUnit height = isHorizontal() ? selHeight : logicalWidth;
239

240
    return LayoutRect(topPoint, LayoutSize(width, height));
eseidel's avatar
eseidel committed
241 242
}

243
void InlineTextBox::deleteLine(RenderArena& arena)
eseidel's avatar
eseidel committed
244
{
245
    renderer().removeTextBox(this);
eseidel's avatar
eseidel committed
246 247 248 249 250
    destroy(arena);
}

void InlineTextBox::extractLine()
{
251
    if (extracted())
eseidel's avatar
eseidel committed
252 253
        return;

254
    renderer().extractTextBox(this);
eseidel's avatar
eseidel committed
255 256 257 258
}

void InlineTextBox::attachLine()
{
259
    if (!extracted())
eseidel's avatar
eseidel committed
260 261
        return;
    
262
    renderer().attachTextBox(this);
eseidel's avatar
eseidel committed
263 264
}

265
float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, float &truncatedWidth, bool& foundBox)
eseidel's avatar
eseidel committed
266 267 268 269 270 271
{
    if (foundBox) {
        m_truncation = cFullTruncation;
        return -1;
    }

dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
272
    // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
273
    float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
eseidel's avatar
eseidel committed
274
    
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
275 276 277
    // 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.
278 279
    bool ltrFullTruncation = flowIsLTR && ellipsisX <= left();
    bool rtlFullTruncation = !flowIsLTR && ellipsisX >= left() + logicalWidth();
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
280 281 282 283 284 285
    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
286

287 288
    bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < right());
    bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > left());
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
289 290
    if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
        foundBox = true;
eseidel's avatar
eseidel committed
291

dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
292 293 294
        // 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.
295
        bool ltr = isLeftToRightDirection();
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
296 297 298
        if (ltr != flowIsLTR) {
          // Width in pixels of the visible portion of the box, excluding the ellipsis.
          int visibleBoxWidth = visibleRightEdge - visibleLeftEdge  - ellipsisWidth;
299
          ellipsisX = ltr ? left() + visibleBoxWidth : right() - visibleBoxWidth;
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
300 301
        }

dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
302 303 304 305 306
        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;
307
            truncatedWidth += ellipsisWidth;
308
            return flowIsLTR ? min(ellipsisX, x()) : max(ellipsisX, right() - ellipsisWidth);
eseidel's avatar
eseidel committed
309
        }
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
310

dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
311
        // Set the truncation index on the text run.
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
312
        m_truncation = offset;
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
313 314 315

        // 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.
316
        float widthOfVisibleText = renderer().width(m_start, offset, textPos(), isFirstLine());
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
317 318 319 320 321 322

        // 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|
323
        truncatedWidth += widthOfVisibleText + ellipsisWidth;
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
324
        if (flowIsLTR)
325
            return left() + widthOfVisibleText;
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
326
        else
327
            return right() - widthOfVisibleText - ellipsisWidth;
eseidel's avatar
eseidel committed
328
    }
329
    truncatedWidth += logicalWidth();
eseidel's avatar
eseidel committed
330 331 332
    return -1;
}

oliver's avatar
oliver committed
333
Color correctedTextColor(Color textColor, Color backgroundColor) 
eseidel's avatar
eseidel committed
334 335 336 337
{
    // 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
338 339 340
    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
341 342 343
        return textColor;
    }
    
adele's avatar
adele committed
344 345
    int distanceFromWhite = differenceSquared(textColor, Color::white);
    int distanceFromBlack = differenceSquared(textColor, Color::black);
eseidel's avatar
eseidel committed
346 347 348 349 350 351 352 353

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

354
void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness, ColorSpace colorSpace)
355
{
356
    TextDrawingModeFlags mode = context->textDrawingMode();
357
    if (strokeThickness > 0) {
358
        TextDrawingModeFlags newMode = mode | TextModeStroke;
359 360 361 362 363 364
        if (mode != newMode) {
            context->setTextDrawingMode(newMode);
            mode = newMode;
        }
    }
    
365
    if (mode & TextModeFill && (fillColor != context->fillColor() || colorSpace != context->fillColorSpace()))
366
        context->setFillColor(fillColor, colorSpace);
367

368
    if (mode & TextModeStroke) {
369
        if (strokeColor != context->strokeColor())
370
            context->setStrokeColor(strokeColor, colorSpace);
371 372 373
        if (strokeThickness != context->strokeThickness())
            context->setStrokeThickness(strokeThickness);
    }
374 375
}

376 377
bool InlineTextBox::isLineBreak() const
{
378
    return renderer().style()->preserveNewline() && len() == 1 && (*renderer().text())[start()] == '\n';
379 380
}

381
bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/)
eseidel's avatar
eseidel committed
382
{
383 384 385
    if (!visibleToHitTesting())
        return false;

386
    if (isLineBreak())
eseidel's avatar
eseidel committed
387 388
        return false;

389 390 391 392 393 394
    if (m_truncation == cFullTruncation)
        return false;

    FloatRect rect(locationIncludingFlipping(), size());
    // Make sure truncated text is ignored while hittesting.
    if (m_truncation != cNoTruncation) {
395
        LayoutUnit widthOfVisibleText = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
396 397

        if (isHorizontal())
398
            renderer().style()->isLeftToRightDirection() ? rect.setWidth(widthOfVisibleText) : rect.shiftXEdgeTo(right() - widthOfVisibleText);
399 400 401 402 403 404 405
        else
            rect.setHeight(widthOfVisibleText);
    }

    rect.moveBy(accumulatedOffset);

    if (locationInContainer.intersects(rect)) {
406
        renderer().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset)));
407
        if (!result.addNodeToRectBasedTestResult(renderer().textNode(), request, locationInContainer, rect))
408
            return true;
eseidel's avatar
eseidel committed
409 410 411 412
    }
    return false;
}

413
FloatSize InlineTextBox::applyShadowToGraphicsContext(GraphicsContext* context, const ShadowData* shadow, const FloatRect& textRect, bool stroked, bool opaque, bool horizontal)
414 415 416 417 418
{
    if (!shadow)
        return FloatSize();

    FloatSize extraOffset;
419 420
    int shadowX = horizontal ? shadow->x() : shadow->y();
    int shadowY = horizontal ? shadow->y() : -shadow->x();
421
    FloatSize shadowOffset(shadowX, shadowY);
422
    int shadowRadius = shadow->radius();
423 424 425 426
    const Color& shadowColor = shadow->color();

    if (shadow->next() || stroked || !opaque) {
        FloatRect shadowRect(textRect);
427
        shadowRect.inflate(shadow->paintingExtent());
428 429 430 431
        shadowRect.move(shadowOffset);
        context->save();
        context->clip(shadowRect);

432
        extraOffset = FloatSize(0, 2 * textRect.height() + max(0.0f, shadowOffset.height()) + shadowRadius);
433 434 435
        shadowOffset -= extraOffset;
    }

436
    context->setShadow(shadowOffset, shadowRadius, shadowColor, context->fillColorSpace());
437 438 439
    return extraOffset;
}

440 441
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
442
{
mitz@apple.com's avatar
mitz@apple.com committed
443
    Color fillColor = context->fillColor();
444
    ColorSpace fillColorSpace = context->fillColorSpace();
mitz@apple.com's avatar
mitz@apple.com committed
445 446
    bool opaque = fillColor.alpha() == 255;
    if (!opaque)
447
        context->setFillColor(Color::black, fillColorSpace);
mitz@apple.com's avatar
mitz@apple.com committed
448

mitz@apple.com's avatar
mitz@apple.com committed
449
    do {
mitz@apple.com's avatar
mitz@apple.com committed
450
        IntSize extraOffset;
451
        if (shadow)
452
            extraOffset = roundedIntSize(InlineTextBox::applyShadowToGraphicsContext(context, shadow, boxRect, stroked, opaque, horizontal));
453
        else if (!opaque)
454
            context->setFillColor(fillColor, fillColorSpace);
mitz@apple.com's avatar
mitz@apple.com committed
455

456 457 458 459 460 461 462 463 464 465 466
        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);
467 468
            }
            if (startOffset < truncationPoint) {
469 470 471 472 473
                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
474 475 476 477 478
        }

        if (!shadow)
            break;

479
        if (shadow->next() || stroked || !opaque)
mitz@apple.com's avatar
mitz@apple.com committed
480 481 482 483
            context->restore();
        else
            context->clearShadow();

484
        shadow = shadow->next();
mitz@apple.com's avatar
mitz@apple.com committed
485
    } while (shadow || stroked || !opaque);
mitz@apple.com's avatar
mitz@apple.com committed
486 487
}

488
bool InlineTextBox::getEmphasisMarkPosition(const RenderStyle& style, TextEmphasisPosition& emphasisPosition) const
489 490
{
    // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
491
    if (style.textEmphasisMark() == TextEmphasisMarkNone)
492 493
        return false;

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

498
    RenderBlock* containingBlock = renderer().containingBlock();
499 500 501 502 503 504
    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.

505
    RenderRubyText* rubyText = toRenderRubyRun(containingBlock->parent())->rubyText();
506 507 508 509 510

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

511 512 513 514 515 516 517 518
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());
}

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

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

527 528 529 530
    LayoutUnit logicalLeftSide = logicalLeftVisualOverflow();
    LayoutUnit logicalRightSide = logicalRightVisualOverflow();
    LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y());
    LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide;
531
    
532 533
    LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
    LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
534
    
535
    LayoutPoint adjustedPaintOffset = roundedIntPoint(paintOffset);
536
    
537
    if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
eseidel's avatar
eseidel committed
538
        return;
darin's avatar
darin committed
539

540
    bool isPrinting = renderer().document().printing();
541
    
eseidel's avatar
eseidel committed
542
    // Determine whether or not we're selected.
543
    bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
weinig's avatar
weinig committed
544
    if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
eseidel's avatar
eseidel committed
545 546 547
        // When only painting the selection, don't bother to paint if there is none.
        return;

dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
548
    if (m_truncation != cNoTruncation) {
549
        if (renderer().containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) {
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
550 551 552 553 554 555 556 557
            // 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|
558
            LayoutUnit widthOfVisibleText = renderer().width(m_start, m_truncation, textPos(), isFirstLine());
559 560
            LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
            LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0);
561
            adjustedPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
dglazkov@chromium.org's avatar
dglazkov@chromium.org committed
562 563 564
        }
    }

mitz@apple.com's avatar
mitz@apple.com committed
565 566
    GraphicsContext* context = paintInfo.context;

567
    const RenderStyle& lineStyle = this->lineStyle();
568
    
569
    adjustedPaintOffset.move(0, lineStyle.isHorizontalWritingMode() ? 0 : -logicalHeight());
570

571
    FloatPoint boxOrigin = locationIncludingFlipping();
572
    boxOrigin.move(adjustedPaintOffset.x(), adjustedPaintOffset.y());
573
    FloatRect boxRect(boxOrigin, LayoutSize(logicalWidth(), logicalHeight()));
574

575
    RenderCombineText* combinedText = lineStyle.hasTextCombine() && renderer().isCombineText() && toRenderCombineText(renderer()).isCombined() ? &toRenderCombineText(renderer()) : 0;
576 577 578 579 580

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

darin's avatar
darin committed
581
    // Determine whether or not we have composition underlines to draw.
582
    bool containsComposition = renderer().textNode() && renderer().frame().editor().compositionNode() == renderer().textNode();
583
    bool useCustomUnderlines = containsComposition && renderer().frame().editor().compositionUsesCustomUnderlines();
eseidel's avatar
eseidel committed
584

585
    // Determine the text colors and selection colors.
586 587
    Color textFillColor;
    Color textStrokeColor;
588
    Color emphasisMarkColor;
589 590
    float textStrokeWidth = lineStyle.textStrokeWidth();
    const ShadowData* textShadow = paintInfo.forceBlackText() ? 0 : lineStyle.textShadow();
weinig's avatar
weinig committed
591

592
    if (paintInfo.forceBlackText()) {
sullivan's avatar
sullivan committed
593 594
        textFillColor = Color::black;
        textStrokeColor = Color::black;
595
        emphasisMarkColor = Color::black;
596
    } else {
597
        textFillColor = lineStyle.visitedDependentColor(CSSPropertyWebkitTextFillColor);
598
        
599 600
        bool forceBackgroundToWhite = false;
        if (isPrinting) {
601
            if (lineStyle.printColorAdjust() == PrintColorAdjustEconomy)
602
                forceBackgroundToWhite = true;
603
            if (renderer().frame().settings().shouldPrintBackgrounds())
604 605 606
                forceBackgroundToWhite = false;
        }

607
        // Make the text fill color legible against a white background
608
        if (forceBackgroundToWhite)
609
            textFillColor = correctedTextColor(textFillColor, Color::white);
mitz@apple.com's avatar
mitz@apple.com committed
610

611
        textStrokeColor = lineStyle.visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
612
        
613
        // Make the text stroke color legible against a white background
614
        if (forceBackgroundToWhite)
615
            textStrokeColor = correctedTextColor(textStrokeColor, Color::white);
616

617
        emphasisMarkColor = lineStyle.visitedDependentColor(CSSPropertyWebkitTextEmphasisColor);
618 619
        
        // Make the text stroke color legible against a white background
620
        if (forceBackgroundToWhite)
621
            emphasisMarkColor = correctedTextColor(emphasisMarkColor, Color::white);
eseidel's avatar
eseidel committed
622 623
    }

weinig's avatar
weinig committed
624
    bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection);
mitz@apple.com's avatar
mitz@apple.com committed
625 626
    bool paintSelectedTextSeparately = false;

627 628
    Color selectionFillColor = textFillColor;
    Color selectionStrokeColor = textStrokeColor;
629
    Color selectionEmphasisMarkColor = emphasisMarkColor;
630
    float selectionStrokeWidth = textStrokeWidth;
631
    const ShadowData* selectionShadow = textShadow;
eseidel's avatar
eseidel committed
632
    if (haveSelection) {
633
        // Check foreground color first.
634
        Color foreground = paintInfo.forceBlackText() ? Color::black : renderer().selectionForegroundColor();
635
        if (foreground.isValid() && foreground != selectionFillColor) {
636 637
            if (!paintSelectedTextOnly)
                paintSelectedTextSeparately = true;
638
            selectionFillColor = foreground;
639
        }
mitz@apple.com's avatar
mitz@apple.com committed
640

641
        Color emphasisMarkForeground = paintInfo.forceBlackText() ? Color::black : renderer().selectionEmphasisMarkColor();
642 643 644 645 646 647
        if (emphasisMarkForeground.isValid() && emphasisMarkForeground != selectionEmphasisMarkColor) {
            if (!paintSelectedTextOnly)
                paintSelectedTextSeparately = true;
            selectionEmphasisMarkColor = emphasisMarkForeground;
        }

648
        if (RenderStyle* pseudoStyle = renderer().getCachedPseudoStyle(SELECTION)) {
649
            const ShadowData* shadow = paintInfo.forceBlackText() ? 0 : pseudoStyle->textShadow();
mitz@apple.com's avatar
mitz@apple.com committed
650
            if (shadow != selectionShadow) {
651 652
                if (!paintSelectedTextOnly)
                    paintSelectedTextSeparately = true;
mitz@apple.com's avatar
mitz@apple.com committed
653
                selectionShadow = shadow;
654
            }
mitz@apple.com's avatar
mitz@apple.com committed
655

hyatt's avatar
hyatt committed
656
            float strokeWidth = pseudoStyle->textStrokeWidth();
657 658 659 660 661 662
            if (strokeWidth != selectionStrokeWidth) {
                if (!paintSelectedTextOnly)
                    paintSelectedTextSeparately = true;
                selectionStrokeWidth = strokeWidth;
            }

663
            Color stroke = paintInfo.forceBlackText() ? Color::black : pseudoStyle->visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
664 665 666 667 668
            if (stroke != selectionStrokeColor) {
                if (!paintSelectedTextOnly)
                    paintSelectedTextSeparately = true;
                selectionStrokeColor = stroke;
            }
eseidel's avatar
eseidel committed
669 670 671
        }
    }

672
    // Set our font.
673
    const Font& font = fontToUse(lineStyle, renderer());
674 675 676 677 678 679 680 681 682 683 684

    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.
685 686
        if (lineStyle.highlight() != nullAtom && !context->paintingDisabled())
            paintCustomHighlight(adjustedPaintOffset, lineStyle.highlight());
687 688 689
#endif

        if (containsComposition && !useCustomUnderlines)
690
            paintCompositionBackground(context, boxOrigin, lineStyle, font,
691 692
                renderer().frame().editor().compositionStart(),
                renderer().frame().editor().compositionEnd());
693

694
        paintDocumentMarkers(context, boxOrigin, lineStyle, font, true);
695 696

        if (haveSelection && !useCustomUnderlines)
697
            paintSelection(context, boxOrigin, lineStyle, font, selectionFillColor);
698 699
    }

700
    if (Page* page = renderer().frame().page()) {
701 702 703 704 705
        // 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)
706
            page->addRelevantRepaintedObject(&renderer(), IntRect(boxOrigin.x(), boxOrigin.y(), logicalWidth(), logicalHeight()));
707 708 709
    }

    // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
710
    int length = m_len;
711
    int maximumLength;
712
    String string;
713
    if (!combinedText) {
714
        string = renderer().text();
715
        if (static_cast<unsigned>(length) != string.length() || m_start) {
716
            ASSERT_WITH_SECURITY_IMPLICATION(static_cast<unsigned>(m_start + length) <= string.length());
717 718
            string = string.substringSharingImpl(m_start, length);
        }
719
        maximumLength = renderer().textLength() - m_start;
720
    } else {
721
        combinedText->getStringToRender(m_start, string, length);
722 723
        maximumLength = length;
    }
724

725
    BufferForAppendingHyphen charactersWithHyphen;
726
    TextRun textRun = constructTextRun(lineStyle, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0);
727
    if (hasHyphen())
728
        length = textRun.length();
mitz@apple.com's avatar
mitz@apple.com committed
729

730 731
    int sPos = 0;
    int ePos = 0;
mitz@apple.com's avatar
mitz@apple.com committed
732
    if (paintSelectedTextOnly || paintSelectedTextSeparately)
eseidel's avatar
eseidel committed
733
        selectionStartEnd(sPos, ePos);
weinig's avatar
weinig committed
734

735 736 737 738 739 740
    if (m_truncation != cNoTruncation) {
        sPos = min<int>(sPos, m_truncation);
        ePos = min<int>(ePos, m_truncation);
        length = m_truncation;
    }

741
    int emphasisMarkOffset = 0;
742
    TextEmphasisPosition emphasisMarkPosition;
743
    bool hasTextEmphasis = getEmphasisMarkPosition(lineStyle, emphasisMarkPosition);
744
    const AtomicString& emphasisMark = hasTextEmphasis ? lineStyle.textEmphasisMarkString() : nullAtom;
745
    if (!emphasisMark.isEmpty())
746
        emphasisMarkOffset = emphasisMarkPosition == TextEmphasisPositionOver ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
747

mitz@apple.com's avatar
mitz@apple.com committed
748 749 750
    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.
751
        GraphicsContextStateSaver stateSaver(*context, textStrokeWidth > 0);
mitz@apple.com's avatar
mitz@apple.com committed
752