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