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 "core/rendering/InlineTextBox.h" 25 26 #include "core/dom/Document.h" 27 #include "core/dom/DocumentMarkerController.h" 28 #include "core/dom/RenderedDocumentMarker.h" 29 #include "core/dom/Text.h" 30 #include "core/editing/Editor.h" 31 #include "core/editing/InputMethodController.h" 32 #include "core/page/Frame.h" 33 #include "core/page/Page.h" 34 #include "core/page/Settings.h" 35 #include "core/platform/graphics/DrawLooper.h" 36 #include "core/platform/graphics/FontCache.h" 37 #include "core/platform/graphics/GraphicsContextStateSaver.h" 38 #include "core/rendering/EllipsisBox.h" 39 #include "core/rendering/HitTestResult.h" 40 #include "core/rendering/PaintInfo.h" 41 #include "core/rendering/RenderBR.h" 42 #include "core/rendering/RenderBlock.h" 43 #include "core/rendering/RenderCombineText.h" 44 #include "core/rendering/RenderRubyRun.h" 45 #include "core/rendering/RenderRubyText.h" 46 #include "core/rendering/RenderTheme.h" 47 #include "core/rendering/style/ShadowData.h" 48 #include "core/rendering/svg/SVGTextRunRenderingContext.h" 49 #include "wtf/Vector.h" 50 #include "wtf/text/CString.h" 51 #include "wtf/text/StringBuilder.h" 52 53 using namespace std; 54 55 namespace WebCore { 56 57 struct SameSizeAsInlineTextBox : public InlineBox { 58 unsigned variables[1]; 59 unsigned short variables2[2]; 60 void* pointers[2]; 61 }; 62 63 COMPILE_ASSERT(sizeof(InlineTextBox) == sizeof(SameSizeAsInlineTextBox), InlineTextBox_should_stay_small); 64 65 typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap; 66 static InlineTextBoxOverflowMap* gTextBoxesWithOverflow; 67 68 static const int misspellingLineThickness = 3; 69 70 void InlineTextBox::destroy() 71 { 72 if (!knownToHaveNoOverflow() && gTextBoxesWithOverflow) 73 gTextBoxesWithOverflow->remove(this); 74 InlineBox::destroy(); 75 } 76 77 void InlineTextBox::markDirty(bool dirty) 78 { 79 if (dirty) { 80 m_len = 0; 81 m_start = 0; 82 } 83 InlineBox::markDirty(dirty); 84 } 85 86 LayoutRect InlineTextBox::logicalOverflowRect() const 87 { 88 if (knownToHaveNoOverflow() || !gTextBoxesWithOverflow) 89 return enclosingIntRect(logicalFrameRect()); 90 return gTextBoxesWithOverflow->get(this); 91 } 92 93 void InlineTextBox::setLogicalOverflowRect(const LayoutRect& rect) 94 { 95 ASSERT(!knownToHaveNoOverflow()); 96 if (!gTextBoxesWithOverflow) 97 gTextBoxesWithOverflow = new InlineTextBoxOverflowMap; 98 gTextBoxesWithOverflow->add(this, rect); 99 } 100 101 int InlineTextBox::baselinePosition(FontBaseline baselineType) const 102 { 103 if (!isText() || !parent()) 104 return 0; 105 if (parent()->renderer() == renderer()->parent()) 106 return parent()->baselinePosition(baselineType); 107 return toRenderBoxModelObject(renderer()->parent())->baselinePosition(baselineType, isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); 108 } 109 110 LayoutUnit InlineTextBox::lineHeight() const 111 { 112 if (!isText() || !renderer()->parent()) 113 return 0; 114 if (m_renderer->isBR()) 115 return toRenderBR(m_renderer)->lineHeight(isFirstLineStyle()); 116 if (parent()->renderer() == renderer()->parent()) 117 return parent()->lineHeight(); 118 return toRenderBoxModelObject(renderer()->parent())->lineHeight(isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); 119 } 120 121 LayoutUnit InlineTextBox::selectionTop() 122 { 123 return root()->selectionTop(); 124 } 125 126 LayoutUnit InlineTextBox::selectionBottom() 127 { 128 return root()->selectionBottom(); 129 } 130 131 LayoutUnit InlineTextBox::selectionHeight() 132 { 133 return root()->selectionHeight(); 134 } 135 136 bool InlineTextBox::isSelected(int startPos, int endPos) const 137 { 138 int sPos = max(startPos - m_start, 0); 139 // The position after a hard line break is considered to be past its end. 140 // See the corresponding code in InlineTextBox::selectionState. 141 int ePos = min(endPos - m_start, int(m_len) + (isLineBreak() ? 0 : 1)); 142 return (sPos < ePos); 143 } 144 145 RenderObject::SelectionState InlineTextBox::selectionState() 146 { 147 RenderObject::SelectionState state = renderer()->selectionState(); 148 if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) { 149 int startPos, endPos; 150 renderer()->selectionStartEnd(startPos, endPos); 151 // The position after a hard line break is considered to be past its end. 152 // See the corresponding code in InlineTextBox::isSelected. 153 int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0); 154 155 // FIXME: Remove -webkit-line-break: LineBreakAfterWhiteSpace. 156 int endOfLineAdjustmentForCSSLineBreak = renderer()->style()->lineBreak() == LineBreakAfterWhiteSpace ? -1 : 0; 157 bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos <= m_start + m_len + endOfLineAdjustmentForCSSLineBreak); 158 bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable); 159 if (start && end) 160 state = RenderObject::SelectionBoth; 161 else if (start) 162 state = RenderObject::SelectionStart; 163 else if (end) 164 state = RenderObject::SelectionEnd; 165 else if ((state == RenderObject::SelectionEnd || startPos < m_start) && 166 (state == RenderObject::SelectionStart || endPos > lastSelectable)) 167 state = RenderObject::SelectionInside; 168 else if (state == RenderObject::SelectionBoth) 169 state = RenderObject::SelectionNone; 170 } 171 172 // If there are ellipsis following, make sure their selection is updated. 173 if (m_truncation != cNoTruncation && root()->ellipsisBox()) { 174 EllipsisBox* ellipsis = root()->ellipsisBox(); 175 if (state != RenderObject::SelectionNone) { 176 int start, end; 177 selectionStartEnd(start, end); 178 // The ellipsis should be considered to be selected if the end of 179 // the selection is past the beginning of the truncation and the 180 // beginning of the selection is before or at the beginning of the 181 // truncation. 182 ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ? 183 RenderObject::SelectionInside : RenderObject::SelectionNone); 184 } else 185 ellipsis->setSelectionState(RenderObject::SelectionNone); 186 } 187 188 return state; 189 } 190 191 LayoutRect InlineTextBox::localSelectionRect(int startPos, int endPos) 192 { 193 int sPos = max(startPos - m_start, 0); 194 int ePos = min(endPos - m_start, (int)m_len); 195 196 if (sPos > ePos) 197 return LayoutRect(); 198 199 FontCachePurgePreventer fontCachePurgePreventer; 200 201 RenderText* textObj = textRenderer(); 202 LayoutUnit selTop = selectionTop(); 203 LayoutUnit selHeight = selectionHeight(); 204 RenderStyle* styleToUse = textObj->style(isFirstLineStyle()); 205 const Font& font = styleToUse->font(); 206 207 StringBuilder charactersWithHyphen; 208 bool respectHyphen = ePos == m_len && hasHyphen(); 209 TextRun textRun = constructTextRun(styleToUse, font, respectHyphen ? &charactersWithHyphen : 0); 210 if (respectHyphen) 211 endPos = textRun.length(); 212 213 FloatPoint startingPoint = FloatPoint(logicalLeft(), selTop); 214 LayoutRect r; 215 if (sPos || ePos != static_cast<int>(m_len)) 216 r = enclosingIntRect(font.selectionRectForText(textRun, startingPoint, selHeight, sPos, ePos)); 217 else // Avoid computing the font width when the entire line box is selected as an optimization. 218 r = enclosingIntRect(FloatRect(startingPoint, FloatSize(m_logicalWidth, selHeight))); 219 220 LayoutUnit logicalWidth = r.width(); 221 if (r.x() > logicalRight()) 222 logicalWidth = 0; 223 else if (r.maxX() > logicalRight()) 224 logicalWidth = logicalRight() - r.x(); 225 226 LayoutPoint topPoint = isHorizontal() ? LayoutPoint(r.x(), selTop) : LayoutPoint(selTop, r.x()); 227 LayoutUnit width = isHorizontal() ? logicalWidth : selHeight; 228 LayoutUnit height = isHorizontal() ? selHeight : logicalWidth; 229 230 return LayoutRect(topPoint, LayoutSize(width, height)); 231 } 232 233 void InlineTextBox::deleteLine() 234 { 235 toRenderText(renderer())->removeTextBox(this); 236 destroy(); 237 } 238 239 void InlineTextBox::extractLine() 240 { 241 if (extracted()) 242 return; 243 244 toRenderText(renderer())->extractTextBox(this); 245 } 246 247 void InlineTextBox::attachLine() 248 { 249 if (!extracted()) 250 return; 251 252 toRenderText(renderer())->attachTextBox(this); 253 } 254 255 float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, float &truncatedWidth, bool& foundBox) 256 { 257 if (foundBox) { 258 m_truncation = cFullTruncation; 259 return -1; 260 } 261 262 // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates. 263 float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth; 264 265 // Criteria for full truncation: 266 // LTR: the left edge of the ellipsis is to the left of our text run. 267 // RTL: the right edge of the ellipsis is to the right of our text run. 268 bool ltrFullTruncation = flowIsLTR && ellipsisX <= left(); 269 bool rtlFullTruncation = !flowIsLTR && ellipsisX >= left() + logicalWidth(); 270 if (ltrFullTruncation || rtlFullTruncation) { 271 // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box. 272 m_truncation = cFullTruncation; 273 foundBox = true; 274 return -1; 275 } 276 277 bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < right()); 278 bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > left()); 279 if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) { 280 foundBox = true; 281 282 // The inline box may have different directionality than it's parent. Since truncation 283 // behavior depends both on both the parent and the inline block's directionality, we 284 // must keep track of these separately. 285 bool ltr = isLeftToRightDirection(); 286 if (ltr != flowIsLTR) { 287 // Width in pixels of the visible portion of the box, excluding the ellipsis. 288 int visibleBoxWidth = visibleRightEdge - visibleLeftEdge - ellipsisWidth; 289 ellipsisX = ltr ? left() + visibleBoxWidth : right() - visibleBoxWidth; 290 } 291 292 int offset = offsetForPosition(ellipsisX, false); 293 if (offset == 0) { 294 // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start 295 // and the ellipsis edge. 296 m_truncation = cFullTruncation; 297 truncatedWidth += ellipsisWidth; 298 return min(ellipsisX, x()); 299 } 300 301 // Set the truncation index on the text run. 302 m_truncation = offset; 303 304 // If we got here that means that we were only partially truncated and we need to return the pixel offset at which 305 // to place the ellipsis. 306 float widthOfVisibleText = toRenderText(renderer())->width(m_start, offset, textPos(), isFirstLineStyle()); 307 308 // The ellipsis needs to be placed just after the last visible character. 309 // Where "after" is defined by the flow directionality, not the inline 310 // box directionality. 311 // e.g. In the case of an LTR inline box truncated in an RTL flow then we can 312 // have a situation such as |Hello| -> |...He| 313 truncatedWidth += widthOfVisibleText + ellipsisWidth; 314 if (flowIsLTR) 315 return left() + widthOfVisibleText; 316 else 317 return right() - widthOfVisibleText - ellipsisWidth; 318 } 319 truncatedWidth += logicalWidth(); 320 return -1; 321 } 322 323 Color correctedTextColor(Color textColor, Color backgroundColor) 324 { 325 // Adjust the text color if it is too close to the background color, 326 // by darkening or lightening it to move it further away. 327 328 int d = differenceSquared(textColor, backgroundColor); 329 // semi-arbitrarily chose 65025 (255^2) value here after a few tests; 330 if (d > 65025) { 331 return textColor; 332 } 333 334 int distanceFromWhite = differenceSquared(textColor, Color::white); 335 int distanceFromBlack = differenceSquared(textColor, Color::black); 336 337 if (distanceFromWhite < distanceFromBlack) { 338 return textColor.dark(); 339 } 340 341 return textColor.light(); 342 } 343 344 void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness) 345 { 346 TextDrawingModeFlags mode = context->textDrawingMode(); 347 if (strokeThickness > 0) { 348 TextDrawingModeFlags newMode = mode | TextModeStroke; 349 if (mode != newMode) { 350 context->setTextDrawingMode(newMode); 351 mode = newMode; 352 } 353 } 354 355 if (mode & TextModeFill && fillColor != context->fillColor()) 356 context->setFillColor(fillColor); 357 358 if (mode & TextModeStroke) { 359 if (strokeColor != context->strokeColor()) 360 context->setStrokeColor(strokeColor); 361 if (strokeThickness != context->strokeThickness()) 362 context->setStrokeThickness(strokeThickness); 363 } 364 } 365 366 bool InlineTextBox::isLineBreak() const 367 { 368 return renderer()->isBR() || (renderer()->style()->preserveNewline() && len() == 1 && (*textRenderer()->text().impl())[start()] == '\n'); 369 } 370 371 bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/) 372 { 373 if (isLineBreak()) 374 return false; 375 376 FloatPoint boxOrigin = locationIncludingFlipping(); 377 boxOrigin.moveBy(accumulatedOffset); 378 FloatRect rect(boxOrigin, size()); 379 if (m_truncation != cFullTruncation && visibleToHitTestRequest(request) && locationInContainer.intersects(rect)) { 380 renderer()->updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset))); 381 if (!result.addNodeToRectBasedTestResult(renderer()->node(), request, locationInContainer, rect)) 382 return true; 383 } 384 return false; 385 } 386 387 static void paintTextWithShadows(GraphicsContext* context, 388 const RenderObject* renderer, const Font& font, const TextRun& textRun, 389 const AtomicString& emphasisMark, int emphasisMarkOffset, 390 int startOffset, int endOffset, int truncationPoint, 391 const FloatPoint& textOrigin, const FloatRect& boxRect, 392 const ShadowData* shadow, bool stroked, bool horizontal) 393 { 394 // Text shadows are disabled when printing. http://crbug.com/258321 395 bool hasShadow = shadow && !context->printing(); 396 Color fillColor = context->fillColor(); 397 398 if (hasShadow) { 399 DrawLooper drawLooper; 400 do { 401 int shadowX = horizontal ? shadow->x() : shadow->y(); 402 int shadowY = horizontal ? shadow->y() : -shadow->x(); 403 FloatSize offset(shadowX, shadowY); 404 drawLooper.addShadow(offset, shadow->blur(), 405 renderer->resolveColor(shadow->color(), Color::stdShadowColor), 406 DrawLooper::ShadowRespectsTransforms, DrawLooper::ShadowIgnoresAlpha); 407 } while ((shadow = shadow->next())); 408 drawLooper.addUnmodifiedContent(); 409 context->setDrawLooper(drawLooper); 410 } 411 412 TextRunPaintInfo textRunPaintInfo(textRun); 413 textRunPaintInfo.bounds = boxRect; 414 if (startOffset <= endOffset) { 415 textRunPaintInfo.from = startOffset; 416 textRunPaintInfo.to = endOffset; 417 if (emphasisMark.isEmpty()) 418 context->drawText(font, textRunPaintInfo, textOrigin); 419 else 420 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset)); 421 } else { 422 if (endOffset > 0) { 423 textRunPaintInfo.from = 0; 424 textRunPaintInfo.to = endOffset; 425 if (emphasisMark.isEmpty()) 426 context->drawText(font, textRunPaintInfo, textOrigin); 427 else 428 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset)); 429 } 430 if (startOffset < truncationPoint) { 431 textRunPaintInfo.from = startOffset; 432 textRunPaintInfo.to = truncationPoint; 433 if (emphasisMark.isEmpty()) 434 context->drawText(font, textRunPaintInfo, textOrigin); 435 else 436 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset)); 437 } 438 } 439 440 if (hasShadow) 441 context->clearDrawLooper(); 442 } 443 444 bool InlineTextBox::getEmphasisMarkPosition(RenderStyle* style, TextEmphasisPosition& emphasisPosition) const 445 { 446 // This function returns true if there are text emphasis marks and they are suppressed by ruby text. 447 if (style->textEmphasisMark() == TextEmphasisMarkNone) 448 return false; 449 450 emphasisPosition = style->textEmphasisPosition(); 451 if (emphasisPosition == TextEmphasisPositionUnder) 452 return true; // Ruby text is always over, so it cannot suppress emphasis marks under. 453 454 RenderBlock* containingBlock = renderer()->containingBlock(); 455 if (!containingBlock->isRubyBase()) 456 return true; // This text is not inside a ruby base, so it does not have ruby text over it. 457 458 if (!containingBlock->parent()->isRubyRun()) 459 return true; // Cannot get the ruby text. 460 461 RenderRubyText* rubyText = toRenderRubyRun(containingBlock->parent())->rubyText(); 462 463 // The emphasis marks over are suppressed only if there is a ruby text box and it not empty. 464 return !rubyText || !rubyText->firstLineBox(); 465 } 466 467 enum RotationDirection { Counterclockwise, Clockwise }; 468 469 static inline AffineTransform rotation(const FloatRect& boxRect, RotationDirection clockwise) 470 { 471 return clockwise ? AffineTransform(0, 1, -1, 0, boxRect.x() + boxRect.maxY(), boxRect.maxY() - boxRect.x()) 472 : AffineTransform(0, -1, 1, 0, boxRect.x() - boxRect.maxY(), boxRect.x() + boxRect.maxY()); 473 } 474 475 void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/) 476 { 477 if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer()->style()->visibility() != VISIBLE || 478 m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len) 479 return; 480 481 ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines); 482 483 LayoutUnit logicalLeftSide = logicalLeftVisualOverflow(); 484 LayoutUnit logicalRightSide = logicalRightVisualOverflow(); 485 LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y()); 486 LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide; 487 488 LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY(); 489 LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y(); 490 491 LayoutPoint adjustedPaintOffset = roundedIntPoint(paintOffset); 492 493 if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart) 494 return; 495 496 bool isPrinting = textRenderer()->document()->printing(); 497 498 // Determine whether or not we're selected. 499 bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone; 500 if (!haveSelection && paintInfo.phase == PaintPhaseSelection) 501 // When only painting the selection, don't bother to paint if there is none. 502 return; 503 504 if (m_truncation != cNoTruncation) { 505 if (renderer()->containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) { 506 // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin 507 // at which we start drawing text. 508 // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is: 509 // |Hello|CBA| -> |...He|CBA| 510 // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing 511 // farther to the right. 512 // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the 513 // truncated string i.e. |Hello|CBA| -> |...lo|CBA| 514 LayoutUnit widthOfVisibleText = toRenderText(renderer())->width(m_start, m_truncation, textPos(), isFirstLineStyle()); 515 LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText; 516 // FIXME: The hit testing logic also needs to take this translation into account. 517 LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0); 518 adjustedPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize()); 519 } 520 } 521 522 GraphicsContext* context = paintInfo.context; 523 524 RenderObject* rendererToUse = renderer(); 525 RenderStyle* styleToUse = rendererToUse->style(isFirstLineStyle()); 526 527 adjustedPaintOffset.move(0, styleToUse->isHorizontalWritingMode() ? 0 : -logicalHeight()); 528 529 FloatPoint boxOrigin = locationIncludingFlipping(); 530 boxOrigin.move(adjustedPaintOffset.x(), adjustedPaintOffset.y()); 531 FloatRect boxRect(boxOrigin, LayoutSize(logicalWidth(), logicalHeight())); 532 533 RenderCombineText* combinedText = styleToUse->hasTextCombine() && textRenderer()->isCombineText() && toRenderCombineText(textRenderer())->isCombined() ? toRenderCombineText(textRenderer()) : 0; 534 535 bool shouldRotate = !isHorizontal() && !combinedText; 536 if (shouldRotate) 537 context->concatCTM(rotation(boxRect, Clockwise)); 538 539 // Determine whether or not we have composition underlines to draw. 540 bool containsComposition = renderer()->node() && renderer()->frame()->inputMethodController().compositionNode() == renderer()->node(); 541 bool useCustomUnderlines = containsComposition && renderer()->frame()->inputMethodController().compositionUsesCustomUnderlines(); 542 543 // Determine the text colors and selection colors. 544 Color textFillColor; 545 Color textStrokeColor; 546 Color emphasisMarkColor; 547 float textStrokeWidth = styleToUse->textStrokeWidth(); 548 549 // Text shadows are disabled when printing. http://crbug.com/258321 550 const ShadowData* textShadow = (context->printing() || paintInfo.forceBlackText()) ? 0 : styleToUse->textShadow(); 551 552 if (paintInfo.forceBlackText()) { 553 textFillColor = Color::black; 554 textStrokeColor = Color::black; 555 emphasisMarkColor = Color::black; 556 } else { 557 textFillColor = rendererToUse->resolveColor(styleToUse, CSSPropertyWebkitTextFillColor); 558 559 bool forceBackgroundToWhite = false; 560 if (isPrinting) { 561 if (styleToUse->printColorAdjust() == PrintColorAdjustEconomy) 562 forceBackgroundToWhite = true; 563 if (textRenderer()->document()->settings() && textRenderer()->document()->settings()->shouldPrintBackgrounds()) 564 forceBackgroundToWhite = false; 565 } 566 567 // Make the text fill color legible against a white background 568 if (forceBackgroundToWhite) 569 textFillColor = correctedTextColor(textFillColor, Color::white); 570 571 textStrokeColor = rendererToUse->resolveColor(styleToUse, CSSPropertyWebkitTextStrokeColor); 572 573 // Make the text stroke color legible against a white background 574 if (forceBackgroundToWhite) 575 textStrokeColor = correctedTextColor(textStrokeColor, Color::white); 576 577 emphasisMarkColor = rendererToUse->resolveColor(styleToUse, CSSPropertyWebkitTextEmphasisColor); 578 579 // Make the text stroke color legible against a white background 580 if (forceBackgroundToWhite) 581 emphasisMarkColor = correctedTextColor(emphasisMarkColor, Color::white); 582 } 583 584 bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection); 585 bool paintSelectedTextSeparately = false; 586 587 Color selectionFillColor = textFillColor; 588 Color selectionStrokeColor = textStrokeColor; 589 Color selectionEmphasisMarkColor = emphasisMarkColor; 590 float selectionStrokeWidth = textStrokeWidth; 591 const ShadowData* selectionShadow = textShadow; 592 if (haveSelection) { 593 // Check foreground color first. 594 Color foreground = paintInfo.forceBlackText() ? Color::black : renderer()->selectionForegroundColor(); 595 if (foreground != Color::transparent && foreground != selectionFillColor) { 596 if (!paintSelectedTextOnly) 597 paintSelectedTextSeparately = true; 598 selectionFillColor = foreground; 599 } 600 601 Color emphasisMarkForeground = paintInfo.forceBlackText() ? Color::black : renderer()->selectionEmphasisMarkColor(); 602 if (emphasisMarkForeground != Color::transparent && emphasisMarkForeground != selectionEmphasisMarkColor) { 603 if (!paintSelectedTextOnly) 604 paintSelectedTextSeparately = true; 605 selectionEmphasisMarkColor = emphasisMarkForeground; 606 } 607 608 if (RenderStyle* pseudoStyle = renderer()->getCachedPseudoStyle(SELECTION)) { 609 // Text shadows are disabled when printing. http://crbug.com/258321 610 const ShadowData* shadow = (context->printing() || paintInfo.forceBlackText()) ? 0 : pseudoStyle->textShadow(); 611 if (shadow != selectionShadow) { 612 if (!paintSelectedTextOnly) 613 paintSelectedTextSeparately = true; 614 selectionShadow = shadow; 615 } 616 617 float strokeWidth = pseudoStyle->textStrokeWidth(); 618 if (strokeWidth != selectionStrokeWidth) { 619 if (!paintSelectedTextOnly) 620 paintSelectedTextSeparately = true; 621 selectionStrokeWidth = strokeWidth; 622 } 623 624 Color stroke = paintInfo.forceBlackText() ? Color::black : rendererToUse->resolveColor(pseudoStyle, CSSPropertyWebkitTextStrokeColor); 625 if (stroke != selectionStrokeColor) { 626 if (!paintSelectedTextOnly) 627 paintSelectedTextSeparately = true; 628 selectionStrokeColor = stroke; 629 } 630 } 631 } 632 633 // Set our font. 634 const Font& font = styleToUse->font(); 635 636 FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent()); 637 638 if (combinedText) 639 combinedText->adjustTextOrigin(textOrigin, boxRect); 640 641 // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection 642 // and composition underlines. 643 if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) { 644 645 if (containsComposition && !useCustomUnderlines) { 646 paintCompositionBackground(context, boxOrigin, styleToUse, font, 647 renderer()->frame()->inputMethodController().compositionStart(), 648 renderer()->frame()->inputMethodController().compositionEnd()); 649 } 650 651 paintDocumentMarkers(context, boxOrigin, styleToUse, font, true); 652 653 if (haveSelection && !useCustomUnderlines) 654 paintSelection(context, boxOrigin, styleToUse, font, selectionFillColor); 655 } 656 657 if (Frame* frame = renderer()->frame()) { 658 if (Page* page = frame->page()) { 659 // FIXME: Right now, InlineTextBoxes never call addRelevantUnpaintedObject() even though they might 660 // legitimately be unpainted if they are waiting on a slow-loading web font. We should fix that, and 661 // when we do, we will have to account for the fact the InlineTextBoxes do not always have unique 662 // renderers and Page currently relies on each unpainted object having a unique renderer. 663 if (paintInfo.phase == PaintPhaseForeground) 664 page->addRelevantRepaintedObject(renderer(), IntRect(boxOrigin.x(), boxOrigin.y(), logicalWidth(), logicalHeight())); 665 } 666 } 667 668 // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only). 669 int length = m_len; 670 int maximumLength; 671 StringView string; 672 if (!combinedText) { 673 string = textRenderer()->text().createView(); 674 if (static_cast<unsigned>(length) != string.length() || m_start) 675 string.narrow(m_start, length); 676 maximumLength = textRenderer()->textLength() - m_start; 677 } else { 678 combinedText->getStringToRender(m_start, string, length); 679 maximumLength = length; 680 } 681 682 StringBuilder charactersWithHyphen; 683 TextRun textRun = constructTextRun(styleToUse, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0); 684 if (hasHyphen()) 685 length = textRun.length(); 686 687 int sPos = 0; 688 int ePos = 0; 689 if (paintSelectedTextOnly || paintSelectedTextSeparately) 690 selectionStartEnd(sPos, ePos); 691 692 if (m_truncation != cNoTruncation) { 693 sPos = min<int>(sPos, m_truncation); 694 ePos = min<int>(ePos, m_truncation); 695 length = m_truncation; 696 } 697 698 int emphasisMarkOffset = 0; 699 TextEmphasisPosition emphasisMarkPosition; 700 bool hasTextEmphasis = getEmphasisMarkPosition(styleToUse, emphasisMarkPosition); 701 const AtomicString& emphasisMark = hasTextEmphasis ? styleToUse->textEmphasisMarkString() : nullAtom; 702 if (!emphasisMark.isEmpty()) 703 emphasisMarkOffset = emphasisMarkPosition == TextEmphasisPositionOver ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark); 704 705 if (!paintSelectedTextOnly) { 706 // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side 707 // effect, so only when we know we're stroking, do a save/restore. 708 GraphicsContextStateSaver stateSaver(*context, textStrokeWidth > 0); 709 710 updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth); 711 if (!paintSelectedTextSeparately || ePos <= sPos) { 712 // FIXME: Truncate right-to-left text correctly. 713 paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); 714 } else { 715 paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); 716 } 717 718 if (!emphasisMark.isEmpty()) { 719 updateGraphicsContext(context, emphasisMarkColor, textStrokeColor, textStrokeWidth); 720 721 DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1)); 722 TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun; 723 FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin; 724 if (combinedText) 725 context->concatCTM(rotation(boxRect, Clockwise)); 726 727 if (!paintSelectedTextSeparately || ePos <= sPos) { 728 // FIXME: Truncate right-to-left text correctly. 729 paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, 0, length, length, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); 730 } else { 731 paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, ePos, sPos, length, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); 732 } 733 734 if (combinedText) 735 context->concatCTM(rotation(boxRect, Counterclockwise)); 736 } 737 } 738 739 if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) { 740 // paint only the text that is selected 741 GraphicsContextStateSaver stateSaver(*context, selectionStrokeWidth > 0); 742 743 updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth); 744 paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal()); 745 if (!emphasisMark.isEmpty()) { 746 updateGraphicsContext(context, selectionEmphasisMarkColor, textStrokeColor, textStrokeWidth); 747 748 DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1)); 749 TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun; 750 FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin; 751 if (combinedText) 752 context->concatCTM(rotation(boxRect, Clockwise)); 753 754 paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, sPos, ePos, length, emphasisMarkTextOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal()); 755 756 if (combinedText) 757 context->concatCTM(rotation(boxRect, Counterclockwise)); 758 } 759 } 760 761 // Paint decorations 762 TextDecoration textDecorations = styleToUse->textDecorationsInEffect(); 763 if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) { 764 updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth); 765 if (combinedText) 766 context->concatCTM(rotation(boxRect, Clockwise)); 767 paintDecoration(context, boxOrigin, textDecorations, styleToUse->textDecorationStyle(), textShadow); 768 if (combinedText) 769 context->concatCTM(rotation(boxRect, Counterclockwise)); 770 } 771 772 if (paintInfo.phase == PaintPhaseForeground) { 773 paintDocumentMarkers(context, boxOrigin, styleToUse, font, false); 774 775 if (useCustomUnderlines) { 776 const Vector<CompositionUnderline>& underlines = renderer()->frame()->inputMethodController().customCompositionUnderlines(); 777 size_t numUnderlines = underlines.size(); 778 779 for (size_t index = 0; index < numUnderlines; ++index) { 780 const CompositionUnderline& underline = underlines[index]; 781 782 if (underline.endOffset <= start()) 783 // underline is completely before this run. This might be an underline that sits 784 // before the first run we draw, or underlines that were within runs we skipped 785 // due to truncation. 786 continue; 787 788 if (underline.startOffset <= end()) { 789 // underline intersects this run. Paint it. 790 paintCompositionUnderline(context, boxOrigin, underline); 791 if (underline.endOffset > end() + 1) 792 // underline also runs into the next run. Bail now, no more marker advancement. 793 break; 794 } else 795 // underline is completely after this run, bail. A later run will paint it. 796 break; 797 } 798 } 799 } 800 801 if (shouldRotate) 802 context->concatCTM(rotation(boxRect, Counterclockwise)); 803 } 804 805 void InlineTextBox::selectionStartEnd(int& sPos, int& ePos) 806 { 807 int startPos, endPos; 808 if (renderer()->selectionState() == RenderObject::SelectionInside) { 809 startPos = 0; 810 endPos = textRenderer()->textLength(); 811 } else { 812 textRenderer()->selectionStartEnd(startPos, endPos); 813 if (renderer()->selectionState() == RenderObject::SelectionStart) 814 endPos = textRenderer()->textLength(); 815 else if (renderer()->selectionState() == RenderObject::SelectionEnd) 816 startPos = 0; 817 } 818 819 sPos = max(startPos - m_start, 0); 820 ePos = min(endPos - m_start, (int)m_len); 821 } 822 823 void alignSelectionRectToDevicePixels(FloatRect& rect) 824 { 825 float maxX = floorf(rect.maxX()); 826 rect.setX(floorf(rect.x())); 827 rect.setWidth(roundf(maxX - rect.x())); 828 } 829 830 void InlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, Color textColor) 831 { 832 if (context->paintingDisabled()) 833 return; 834 835 // See if we have a selection to paint at all. 836 int sPos, ePos; 837 selectionStartEnd(sPos, ePos); 838 if (sPos >= ePos) 839 return; 840 841 Color c = renderer()->selectionBackgroundColor(); 842 if (!c.alpha()) 843 return; 844 845 // If the text color ends up being the same as the selection background, invert the selection 846 // background. 847 if (textColor == c) 848 c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue()); 849 850 GraphicsContextStateSaver stateSaver(*context); 851 updateGraphicsContext(context, c, c, 0); // Don't draw text at all! 852 853 // If the text is truncated, let the thing being painted in the truncation 854 // draw its own highlight. 855 int length = m_truncation != cNoTruncation ? m_truncation : m_len; 856 StringView string = textRenderer()->text().createView(); 857 858 if (string.length() != static_cast<unsigned>(length) || m_start) 859 string.narrow(m_start, length); 860 861 StringBuilder charactersWithHyphen; 862 bool respectHyphen = ePos == length && hasHyphen(); 863 TextRun textRun = constructTextRun(style, font, string, textRenderer()->textLength() - m_start, respectHyphen ? &charactersWithHyphen : 0); 864 if (respectHyphen) 865 ePos = textRun.length(); 866 867 LayoutUnit selectionBottom = root()->selectionBottom(); 868 LayoutUnit selectionTop = root()->selectionTopAdjustedForPrecedingBlock(); 869 870 int deltaY = roundToInt(renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop); 871 int selHeight = max(0, roundToInt(selectionBottom - selectionTop)); 872 873 FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY); 874 FloatRect clipRect(localOrigin, FloatSize(m_logicalWidth, selHeight)); 875 alignSelectionRectToDevicePixels(clipRect); 876 877 context->clip(clipRect); 878 879 context->drawHighlightForText(font, textRun, localOrigin, selHeight, c, sPos, ePos); 880 } 881 882 void InlineTextBox::paintCompositionBackground(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, int startPos, int endPos) 883 { 884 int offset = m_start; 885 int sPos = max(startPos - offset, 0); 886 int ePos = min(endPos - offset, (int)m_len); 887 888 if (sPos >= ePos) 889 return; 890 891 GraphicsContextStateSaver stateSaver(*context); 892 893 Color c = Color(225, 221, 85); 894 895 updateGraphicsContext(context, c, c, 0); // Don't draw text at all! 896 897 int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); 898 int selHeight = selectionHeight(); 899 FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY); 900 context->drawHighlightForText(font, constructTextRun(style, font), localOrigin, selHeight, c, sPos, ePos); 901 } 902 903 static StrokeStyle textDecorationStyleToStrokeStyle(TextDecorationStyle decorationStyle) 904 { 905 StrokeStyle strokeStyle = SolidStroke; 906 switch (decorationStyle) { 907 case TextDecorationStyleSolid: 908 strokeStyle = SolidStroke; 909 break; 910 case TextDecorationStyleDouble: 911 strokeStyle = DoubleStroke; 912 break; 913 case TextDecorationStyleDotted: 914 strokeStyle = DottedStroke; 915 break; 916 case TextDecorationStyleDashed: 917 strokeStyle = DashedStroke; 918 break; 919 case TextDecorationStyleWavy: 920 strokeStyle = WavyStroke; 921 break; 922 } 923 924 return strokeStyle; 925 } 926 927 #if ENABLE(CSS3_TEXT) 928 static int computeUnderlineOffset(const TextUnderlinePosition underlinePosition, const FontMetrics& fontMetrics, const InlineTextBox* inlineTextBox, const int textDecorationThickness) 929 { 930 // Compute the gap between the font and the underline. Use at least one 931 // pixel gap, if underline is thick then use a bigger gap. 932 const int gap = max<int>(1, ceilf(textDecorationThickness / 2.0)); 933 934 // According to the specification TextUnderlinePositionAuto should default to 'alphabetic' for horizontal text 935 // and to 'under Left' for vertical text (e.g. japanese). We support only horizontal text for now. 936 switch (underlinePosition) { 937 case TextUnderlinePositionAlphabetic: 938 case TextUnderlinePositionAuto: 939 return fontMetrics.ascent() + gap; // Position underline near the alphabetic baseline. 940 case TextUnderlinePositionUnder: { 941 // Position underline relative to the under edge of the lowest element's content box. 942 const float offset = inlineTextBox->root()->maxLogicalTop() - inlineTextBox->logicalTop(); 943 if (offset > 0) 944 return inlineTextBox->logicalHeight() + gap + offset; 945 return inlineTextBox->logicalHeight() + gap; 946 } 947 } 948 949 ASSERT_NOT_REACHED(); 950 return fontMetrics.ascent() + gap; 951 } 952 #endif // CSS3_TEXT 953 954 static void adjustStepToDecorationLength(float& step, float& controlPointDistance, float length) 955 { 956 ASSERT(step > 0); 957 958 if (length <= 0) 959 return; 960 961 unsigned stepCount = static_cast<unsigned>(length / step); 962 963 // Each Bezier curve starts at the same pixel that the previous one 964 // ended. We need to subtract (stepCount - 1) pixels when calculating the 965 // length covered to account for that. 966 float uncoveredLength = length - (stepCount * step - (stepCount - 1)); 967 float adjustment = uncoveredLength / stepCount; 968 step += adjustment; 969 controlPointDistance += adjustment; 970 } 971 972 /* 973 * Draw one cubic Bezier curve and repeat the same pattern long the the decoration's axis. 974 * The start point (p1), controlPoint1, controlPoint2 and end point (p2) of the Bezier curve 975 * form a diamond shape: 976 * 977 * step 978 * |-----------| 979 * 980 * controlPoint1 981 * + 982 * 983 * 984 * . . 985 * . . 986 * . . 987 * (x1, y1) p1 + . + p2 (x2, y2) - <--- Decoration's axis 988 * . . | 989 * . . | 990 * . . | controlPointDistance 991 * | 992 * | 993 * + - 994 * controlPoint2 995 * 996 * |-----------| 997 * step 998 */ 999 static void strokeWavyTextDecoration(GraphicsContext* context, FloatPoint& p1, FloatPoint& p2, float strokeThickness) 1000 { 1001 context->adjustLineToPixelBoundaries(p1, p2, strokeThickness, context->strokeStyle()); 1002 1003 Path path; 1004 path.moveTo(p1); 1005 1006 // Distance between decoration's axis and Bezier curve's control points. 1007 // The height of the curve is based on this distance. Use a minimum of 6 pixels distance since 1008 // the actual curve passes approximately at half of that distance, that is 3 pixels. 1009 // The minimum height of the curve is also approximately 3 pixels. Increases the curve's height 1010 // as strockThickness increases to make the curve looks better. 1011 float controlPointDistance = 3 * max<float>(2, strokeThickness); 1012 1013 // Increment used to form the diamond shape between start point (p1), control 1014 // points and end point (p2) along the axis of the decoration. Makes the 1015 // curve wider as strockThickness increases to make the curve looks better. 1016 float step = 2 * max<float>(2, strokeThickness); 1017 1018 bool isVerticalLine = (p1.x() == p2.x()); 1019 1020 if (isVerticalLine) { 1021 ASSERT(p1.x() == p2.x()); 1022 1023 float xAxis = p1.x(); 1024 float y1; 1025 float y2; 1026 1027 if (p1.y() < p2.y()) { 1028 y1 = p1.y(); 1029 y2 = p2.y(); 1030 } else { 1031 y1 = p2.y(); 1032 y2 = p1.y(); 1033 } 1034 1035 adjustStepToDecorationLength(step, controlPointDistance, y2 - y1); 1036 FloatPoint controlPoint1(xAxis + controlPointDistance, 0); 1037 FloatPoint controlPoint2(xAxis - controlPointDistance, 0); 1038 1039 for (float y = y1; y + 2 * step <= y2;) { 1040 controlPoint1.setY(y + step); 1041 controlPoint2.setY(y + step); 1042 y += 2 * step; 1043 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y)); 1044 } 1045 } else { 1046 ASSERT(p1.y() == p2.y()); 1047 1048 float yAxis = p1.y(); 1049 float x1; 1050 float x2; 1051 1052 if (p1.x() < p2.x()) { 1053 x1 = p1.x(); 1054 x2 = p2.x(); 1055 } else { 1056 x1 = p2.x(); 1057 x2 = p1.x(); 1058 } 1059 1060 adjustStepToDecorationLength(step, controlPointDistance, x2 - x1); 1061 FloatPoint controlPoint1(0, yAxis + controlPointDistance); 1062 FloatPoint controlPoint2(0, yAxis - controlPointDistance); 1063 1064 for (float x = x1; x + 2 * step <= x2;) { 1065 controlPoint1.setX(x + step); 1066 controlPoint2.setX(x + step); 1067 x += 2 * step; 1068 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis)); 1069 } 1070 } 1071 1072 context->setShouldAntialias(true); 1073 context->strokePath(path); 1074 } 1075 1076 void InlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& boxOrigin, TextDecoration deco, TextDecorationStyle decorationStyle, const ShadowData* shadow) 1077 { 1078 GraphicsContextStateSaver stateSaver(*context); 1079 1080 // FIXME: We should improve this rule and not always just assume 1. 1081 const float textDecorationThickness = 1.f; 1082 1083 if (m_truncation == cFullTruncation) 1084 return; 1085 1086 FloatPoint localOrigin = boxOrigin; 1087 1088 float width = m_logicalWidth; 1089 if (m_truncation != cNoTruncation) { 1090 width = toRenderText(renderer())->width(m_start, m_truncation, textPos(), isFirstLineStyle()); 1091 if (!isLeftToRightDirection()) 1092 localOrigin.move(m_logicalWidth - width, 0); 1093 } 1094 1095 // Get the text decoration colors. 1096 Color underline, overline, linethrough; 1097 renderer()->getTextDecorationColors(deco, underline, overline, linethrough, true); 1098 if (isFirstLineStyle()) 1099 renderer()->getTextDecorationColors(deco, underline, overline, linethrough, true, true); 1100 1101 // Use a special function for underlines to get the positioning exactly right. 1102 bool isPrinting = textRenderer()->document()->printing(); 1103 context->setStrokeThickness(textDecorationThickness); 1104 1105 bool linesAreOpaque = !isPrinting && (!(deco & TextDecorationUnderline) || underline.alpha() == 255) && (!(deco & TextDecorationOverline) || overline.alpha() == 255) && (!(deco & TextDecorationLineThrough) || linethrough.alpha() == 255); 1106 1107 RenderStyle* styleToUse = renderer()->style(isFirstLineStyle()); 1108 int baseline = styleToUse->fontMetrics().ascent(); 1109 1110 int extraOffset = 0; 1111 if (!linesAreOpaque && shadow && shadow->next()) { 1112 FloatRect clipRect(localOrigin, FloatSize(width, baseline + 2)); 1113 for (const ShadowData* s = shadow; s; s = s->next()) { 1114 FloatRect shadowRect(localOrigin, FloatSize(width, baseline + 2)); 1115 shadowRect.inflate(s->blur()); 1116 int shadowX = isHorizontal() ? s->x() : s->y(); 1117 int shadowY = isHorizontal() ? s->y() : -s->x(); 1118 shadowRect.move(shadowX, shadowY); 1119 clipRect.unite(shadowRect); 1120 extraOffset = max(extraOffset, max(0, shadowY) + s->blur()); 1121 } 1122 context->clip(clipRect); 1123 extraOffset += baseline + 2; 1124 localOrigin.move(0, extraOffset); 1125 } 1126 1127 do { 1128 if (shadow) { 1129 if (!shadow->next()) { 1130 // The last set of lines paints normally inside the clip. 1131 localOrigin.move(0, -extraOffset); 1132 extraOffset = 0; 1133 } 1134 int shadowX = isHorizontal() ? shadow->x() : shadow->y(); 1135 int shadowY = isHorizontal() ? shadow->y() : -shadow->x(); 1136 context->setShadow(FloatSize(shadowX, shadowY - extraOffset), shadow->blur(), 1137 renderer()->resolveColor(shadow->color(), Color::stdShadowColor)); 1138 shadow = shadow->next(); 1139 } 1140 1141 // Offset between lines - always non-zero, so lines never cross each other. 1142 float doubleOffset = textDecorationThickness + 1.f; 1143 context->setStrokeStyle(textDecorationStyleToStrokeStyle(decorationStyle)); 1144 if (deco & TextDecorationUnderline) { 1145 context->setStrokeColor(underline); 1146 #if ENABLE(CSS3_TEXT) 1147 TextUnderlinePosition underlinePosition = styleToUse->textUnderlinePosition(); 1148 const int underlineOffset = computeUnderlineOffset(underlinePosition, styleToUse->fontMetrics(), this, textDecorationThickness); 1149 #else 1150 const int underlineOffset = styleToUse->fontMetrics().ascent() + max<int>(1, ceilf(textDecorationThickness / 2.0)); 1151 #endif // CSS3_TEXT 1152 1153 switch (decorationStyle) { 1154 case TextDecorationStyleWavy: { 1155 FloatPoint start(localOrigin.x(), localOrigin.y() + underlineOffset + doubleOffset); 1156 FloatPoint end(localOrigin.x() + width, localOrigin.y() + underlineOffset + doubleOffset); 1157 strokeWavyTextDecoration(context, start, end, textDecorationThickness); 1158 break; 1159 } 1160 default: 1161 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset), width, isPrinting); 1162 1163 if (decorationStyle == TextDecorationStyleDouble) 1164 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset + doubleOffset), width, isPrinting); 1165 } 1166 } 1167 if (deco & TextDecorationOverline) { 1168 context->setStrokeColor(overline); 1169 switch (decorationStyle) { 1170 case TextDecorationStyleWavy: { 1171 FloatPoint start(localOrigin.x(), localOrigin.y() - doubleOffset); 1172 FloatPoint end(localOrigin.x() + width, localOrigin.y() - doubleOffset); 1173 strokeWavyTextDecoration(context, start, end, textDecorationThickness); 1174 break; 1175 } 1176 default: 1177 context->drawLineForText(localOrigin, width, isPrinting); 1178 if (decorationStyle == TextDecorationStyleDouble) 1179 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() - doubleOffset), width, isPrinting); 1180 } 1181 } 1182 if (deco & TextDecorationLineThrough) { 1183 context->setStrokeColor(linethrough); 1184 switch (decorationStyle) { 1185 case TextDecorationStyleWavy: { 1186 FloatPoint start(localOrigin.x(), localOrigin.y() + 2 * baseline / 3); 1187 FloatPoint end(localOrigin.x() + width, localOrigin.y() + 2 * baseline / 3); 1188 strokeWavyTextDecoration(context, start, end, textDecorationThickness); 1189 break; 1190 } 1191 default: 1192 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + 2 * baseline / 3), width, isPrinting); 1193 if (decorationStyle == TextDecorationStyleDouble) 1194 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + doubleOffset + 2 * baseline / 3), width, isPrinting); 1195 } 1196 } 1197 } while (shadow); 1198 } 1199 1200 static GraphicsContext::DocumentMarkerLineStyle lineStyleForMarkerType(DocumentMarker::MarkerType markerType) 1201 { 1202 switch (markerType) { 1203 case DocumentMarker::Spelling: 1204 return GraphicsContext::DocumentMarkerSpellingLineStyle; 1205 case DocumentMarker::Grammar: 1206 return GraphicsContext::DocumentMarkerGrammarLineStyle; 1207 default: 1208 ASSERT_NOT_REACHED(); 1209 return GraphicsContext::DocumentMarkerSpellingLineStyle; 1210 } 1211 } 1212 1213 void InlineTextBox::paintDocumentMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, RenderStyle* style, const Font& font, bool grammar) 1214 { 1215 // Never print spelling/grammar markers (5327887) 1216 if (textRenderer()->document()->printing()) 1217 return; 1218 1219 if (m_truncation == cFullTruncation) 1220 return; 1221 1222 float start = 0; // start of line to draw, relative to tx 1223 float width = m_logicalWidth; // how much line to draw 1224 1225 // Determine whether we need to measure text 1226 bool markerSpansWholeBox = true; 1227 if (m_start <= (int)marker->startOffset()) 1228 markerSpansWholeBox = false; 1229 if ((end() + 1) != marker->endOffset()) // end points at the last char, not past it 1230 markerSpansWholeBox = false; 1231 if (m_truncation != cNoTruncation) 1232 markerSpansWholeBox = false; 1233 1234 if (!markerSpansWholeBox || grammar) { 1235 int startPosition = max<int>(marker->startOffset() - m_start, 0); 1236 int endPosition = min<int>(marker->endOffset() - m_start, m_len); 1237 1238 if (m_truncation != cNoTruncation) 1239 endPosition = min<int>(endPosition, m_truncation); 1240 1241 // Calculate start & width 1242 int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); 1243 int selHeight = selectionHeight(); 1244 FloatPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY); 1245 TextRun run = constructTextRun(style, font); 1246 1247 // FIXME: Convert the document markers to float rects. 1248 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, selHeight, startPosition, endPosition)); 1249 start = markerRect.x() - startPoint.x(); 1250 width = markerRect.width(); 1251 1252 // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to 1253 // display a toolTip. We don't do this for misspelling markers. 1254 if (grammar) { 1255 markerRect.move(-boxOrigin.x(), -boxOrigin.y()); 1256 markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); 1257 toRenderedDocumentMarker(marker)->setRenderedRect(markerRect); 1258 } 1259 } 1260 1261 // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to 1262 // make sure to fit within those bounds. This means the top pixel(s) of the underline will overlap the 1263 // bottom pixel(s) of the glyphs in smaller font sizes. The alternatives are to increase the line spacing (bad!!) 1264 // or decrease the underline thickness. The overlap is actually the most useful, and matches what AppKit does. 1265 // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so 1266 // we pin to two pixels under the baseline. 1267 int lineThickness = misspellingLineThickness; 1268 int baseline = renderer()->style(isFirstLineStyle())->fontMetrics().ascent(); 1269 int descent = logicalHeight() - baseline; 1270 int underlineOffset; 1271 if (descent <= (2 + lineThickness)) { 1272 // Place the underline at the very bottom of the text in small/medium fonts. 1273 underlineOffset = logicalHeight() - lineThickness; 1274 } else { 1275 // In larger fonts, though, place the underline up near the baseline to prevent a big gap. 1276 underlineOffset = baseline + 2; 1277 } 1278 pt->drawLineForDocumentMarker(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, lineStyleForMarkerType(marker->type())); 1279 } 1280 1281 void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, RenderStyle* style, const Font& font) 1282 { 1283 // Use same y positioning and height as for selection, so that when the selection and this highlight are on 1284 // the same word there are no pieces sticking out. 1285 int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); 1286 int selHeight = selectionHeight(); 1287 1288 int sPos = max(marker->startOffset() - m_start, (unsigned)0); 1289 int ePos = min(marker->endOffset() - m_start, (unsigned)m_len); 1290 TextRun run = constructTextRun(style, font); 1291 1292 // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates. 1293 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, IntPoint(x(), selectionTop()), selHeight, sPos, ePos)); 1294 markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); 1295 toRenderedDocumentMarker(marker)->setRenderedRect(markerRect); 1296 1297 // Optionally highlight the text 1298 if (renderer()->frame()->editor()->markedTextMatchesAreHighlighted()) { 1299 Color color = marker->activeMatch() ? 1300 renderer()->theme()->platformActiveTextSearchHighlightColor() : 1301 renderer()->theme()->platformInactiveTextSearchHighlightColor(); 1302 GraphicsContextStateSaver stateSaver(*pt); 1303 updateGraphicsContext(pt, color, color, 0); // Don't draw text at all! 1304 pt->clip(FloatRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selHeight)); 1305 pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, sPos, ePos); 1306 } 1307 } 1308 1309 void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, bool background) 1310 { 1311 if (!renderer()->node()) 1312 return; 1313 1314 Vector<DocumentMarker*> markers = renderer()->document()->markers()->markersFor(renderer()->node()); 1315 Vector<DocumentMarker*>::const_iterator markerIt = markers.begin(); 1316 1317 // Give any document markers that touch this run a chance to draw before the text has been drawn. 1318 // Note end() points at the last char, not one past it like endOffset and ranges do. 1319 for ( ; markerIt != markers.end(); markerIt++) { 1320 DocumentMarker* marker = *markerIt; 1321 1322 // Paint either the background markers or the foreground markers, but not both 1323 switch (marker->type()) { 1324 case DocumentMarker::Grammar: 1325 case DocumentMarker::Spelling: 1326 if (background) 1327 continue; 1328 break; 1329 case DocumentMarker::TextMatch: 1330 if (!background) 1331 continue; 1332 break; 1333 default: 1334 continue; 1335 } 1336 1337 if (marker->endOffset() <= start()) 1338 // marker is completely before this run. This might be a marker that sits before the 1339 // first run we draw, or markers that were within runs we skipped due to truncation. 1340 continue; 1341 1342 if (marker->startOffset() > end()) 1343 // marker is completely after this run, bail. A later run will paint it. 1344 break; 1345 1346 // marker intersects this run. Paint it. 1347 switch (marker->type()) { 1348 case DocumentMarker::Spelling: 1349 paintDocumentMarker(pt, boxOrigin, marker, style, font, false); 1350 break; 1351 case DocumentMarker::Grammar: 1352 paintDocumentMarker(pt, boxOrigin, marker, style, font, true); 1353 break; 1354 case DocumentMarker::TextMatch: 1355 paintTextMatchMarker(pt, boxOrigin, marker, style, font); 1356 break; 1357 default: 1358 ASSERT_NOT_REACHED(); 1359 } 1360 1361 } 1362 } 1363 1364 void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const FloatPoint& boxOrigin, const CompositionUnderline& underline) 1365 { 1366 if (m_truncation == cFullTruncation) 1367 return; 1368 1369 float start = 0; // start of line to draw, relative to tx 1370 float width = m_logicalWidth; // how much line to draw 1371 bool useWholeWidth = true; 1372 unsigned paintStart = m_start; 1373 unsigned paintEnd = end() + 1; // end points at the last char, not past it 1374 if (paintStart <= underline.startOffset) { 1375 paintStart = underline.startOffset; 1376 useWholeWidth = false; 1377 start = toRenderText(renderer())->width(m_start, paintStart - m_start, textPos(), isFirstLineStyle()); 1378 } 1379 if (paintEnd != underline.endOffset) { // end points at the last char, not past it 1380 paintEnd = min(paintEnd, (unsigned)underline.endOffset); 1381 useWholeWidth = false; 1382 } 1383 if (m_truncation != cNoTruncation) { 1384 paintEnd = min(paintEnd, (unsigned)m_start + m_truncation); 1385 useWholeWidth = false; 1386 } 1387 if (!useWholeWidth) { 1388 width = toRenderText(renderer())->width(paintStart, paintEnd - paintStart, textPos() + start, isFirstLineStyle()); 1389 } 1390 1391 // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline. 1392 // All other marked text underlines are 1px thick. 1393 // If there's not enough space the underline will touch or overlap characters. 1394 int lineThickness = 1; 1395 int baseline = renderer()->style(isFirstLineStyle())->fontMetrics().ascent(); 1396 if (underline.thick && logicalHeight() - baseline >= 2) 1397 lineThickness = 2; 1398 1399 // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those. 1400 // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too. 1401 start += 1; 1402 width -= 2; 1403 1404 ctx->setStrokeColor(underline.color); 1405 ctx->setStrokeThickness(lineThickness); 1406 ctx->drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, textRenderer()->document()->printing()); 1407 } 1408 1409 int InlineTextBox::caretMinOffset() const 1410 { 1411 return m_start; 1412 } 1413 1414 int InlineTextBox::caretMaxOffset() const 1415 { 1416 return m_start + m_len; 1417 } 1418 1419 float InlineTextBox::textPos() const 1420 { 1421 // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset 1422 // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width. 1423 if (logicalLeft() == 0) 1424 return 0; 1425 return logicalLeft() - root()->logicalLeft(); 1426 } 1427 1428 int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs) const 1429 { 1430 if (isLineBreak()) 1431 return 0; 1432 1433 if (lineOffset - logicalLeft() > logicalWidth()) 1434 return isLeftToRightDirection() ? len() : 0; 1435 if (lineOffset - logicalLeft() < 0) 1436 return isLeftToRightDirection() ? 0 : len(); 1437 1438 FontCachePurgePreventer fontCachePurgePreventer; 1439 1440 RenderText* text = toRenderText(renderer()); 1441 RenderStyle* style = text->style(isFirstLineStyle()); 1442 const Font& font = style->font(); 1443 return font.offsetForPosition(constructTextRun(style, font), lineOffset - logicalLeft(), includePartialGlyphs); 1444 } 1445 1446 float InlineTextBox::positionForOffset(int offset) const 1447 { 1448 ASSERT(offset >= m_start); 1449 ASSERT(offset <= m_start + m_len); 1450 1451 if (isLineBreak()) 1452 return logicalLeft(); 1453 1454 FontCachePurgePreventer fontCachePurgePreventer; 1455 1456 RenderText* text = toRenderText(renderer()); 1457 RenderStyle* styleToUse = text->style(isFirstLineStyle()); 1458 ASSERT(styleToUse); 1459 const Font& font = styleToUse->font(); 1460 int from = !isLeftToRightDirection() ? offset - m_start : 0; 1461 int to = !isLeftToRightDirection() ? m_len : offset - m_start; 1462 // FIXME: Do we need to add rightBearing here? 1463 return font.selectionRectForText(constructTextRun(styleToUse, font), IntPoint(logicalLeft(), 0), 0, from, to).maxX(); 1464 } 1465 1466 bool InlineTextBox::containsCaretOffset(int offset) const 1467 { 1468 // Offsets before the box are never "in". 1469 if (offset < m_start) 1470 return false; 1471 1472 int pastEnd = m_start + m_len; 1473 1474 // Offsets inside the box (not at either edge) are always "in". 1475 if (offset < pastEnd) 1476 return true; 1477 1478 // Offsets outside the box are always "out". 1479 if (offset > pastEnd) 1480 return false; 1481 1482 // Offsets at the end are "out" for line breaks (they are on the next line). 1483 if (isLineBreak()) 1484 return false; 1485 1486 // Offsets at the end are "in" for normal boxes (but the caller has to check affinity). 1487 return true; 1488 } 1489 1490 TextRun InlineTextBox::constructTextRun(RenderStyle* style, const Font& font, StringBuilder* charactersWithHyphen) const 1491 { 1492 ASSERT(style); 1493 1494 RenderText* textRenderer = this->textRenderer(); 1495 ASSERT(textRenderer); 1496 ASSERT(textRenderer->text()); 1497 1498 StringView string = textRenderer->text().createView(); 1499 unsigned startPos = start(); 1500 unsigned length = len(); 1501 1502 if (string.length() != length || startPos) 1503 string.narrow(startPos, length); 1504 1505 return constructTextRun(style, font, string, textRenderer->textLength() - startPos, charactersWithHyphen); 1506 } 1507 1508 TextRun InlineTextBox::constructTextRun(RenderStyle* style, const Font& font, StringView string, int maximumLength, StringBuilder* charactersWithHyphen) const 1509 { 1510 ASSERT(style); 1511 1512 RenderText* textRenderer = this->textRenderer(); 1513 ASSERT(textRenderer); 1514 1515 if (charactersWithHyphen) { 1516 const AtomicString& hyphenString = style->hyphenString(); 1517 charactersWithHyphen->reserveCapacity(string.length() + hyphenString.length()); 1518 charactersWithHyphen->append(string); 1519 charactersWithHyphen->append(hyphenString); 1520 string = charactersWithHyphen->toString().createView(); 1521 maximumLength = string.length(); 1522 } 1523 1524 ASSERT(maximumLength >= static_cast<int>(string.length())); 1525 1526 TextRun run(string, textPos(), expansion(), expansionBehavior(), direction(), dirOverride() || style->rtlOrdering() == VisualOrder, !textRenderer->canUseSimpleFontCodePath()); 1527 run.setTabSize(!style->collapseWhiteSpace(), style->tabSize()); 1528 if (textRunNeedsRenderingContext(font)) 1529 run.setRenderingContext(SVGTextRunRenderingContext::create(textRenderer)); 1530 1531 // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring. 1532 run.setCharactersLength(maximumLength); 1533 ASSERT(run.charactersLength() >= run.length()); 1534 return run; 1535 } 1536 1537 #ifndef NDEBUG 1538 1539 const char* InlineTextBox::boxName() const 1540 { 1541 return "InlineTextBox"; 1542 } 1543 1544 void InlineTextBox::showBox(int printedCharacters) const 1545 { 1546 const RenderText* obj = toRenderText(renderer()); 1547 String value = obj->text(); 1548 value = value.substring(start(), len()); 1549 value.replaceWithLiteral('\\', "\\\\"); 1550 value.replaceWithLiteral('\n', "\\n"); 1551 printedCharacters += fprintf(stderr, "%s\t%p", boxName(), this); 1552 for (; printedCharacters < showTreeCharacterOffset; printedCharacters++) 1553 fputc(' ', stderr); 1554 printedCharacters = fprintf(stderr, "\t%s %p", obj->renderName(), obj); 1555 const int rendererCharacterOffset = 24; 1556 for (; printedCharacters < rendererCharacterOffset; printedCharacters++) 1557 fputc(' ', stderr); 1558 fprintf(stderr, "(%d,%d) \"%s\"\n", start(), start() + len(), value.utf8().data()); 1559 } 1560 1561 #endif 1562 1563 } // namespace WebCore 1564