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