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