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