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