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