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