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