1 /* 2 * (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 2000 Dirk Mueller (mueller (at) kde.org) 4 * Copyright (C) 2004, 2005, 2006, 2007, 2008 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 "Editor.h" 30 #include "EllipsisBox.h" 31 #include "Frame.h" 32 #include "GraphicsContext.h" 33 #include "HitTestResult.h" 34 #include "Page.h" 35 #include "RenderArena.h" 36 #include "RenderBlock.h" 37 #include "RenderTheme.h" 38 #include "Text.h" 39 #include "break_lines.h" 40 #include <wtf/AlwaysInline.h> 41 42 using namespace std; 43 44 namespace WebCore { 45 46 int InlineTextBox::selectionTop() 47 { 48 return root()->selectionTop(); 49 } 50 51 int InlineTextBox::selectionHeight() 52 { 53 return root()->selectionHeight(); 54 } 55 56 bool InlineTextBox::isSelected(int startPos, int endPos) const 57 { 58 int sPos = max(startPos - m_start, 0); 59 int ePos = min(endPos - m_start, (int)m_len); 60 return (sPos < ePos); 61 } 62 63 RenderObject::SelectionState InlineTextBox::selectionState() 64 { 65 RenderObject::SelectionState state = renderer()->selectionState(); 66 if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) { 67 int startPos, endPos; 68 renderer()->selectionStartEnd(startPos, endPos); 69 // The position after a hard line break is considered to be past its end. 70 int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0); 71 72 bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len); 73 bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable); 74 if (start && end) 75 state = RenderObject::SelectionBoth; 76 else if (start) 77 state = RenderObject::SelectionStart; 78 else if (end) 79 state = RenderObject::SelectionEnd; 80 else if ((state == RenderObject::SelectionEnd || startPos < m_start) && 81 (state == RenderObject::SelectionStart || endPos > lastSelectable)) 82 state = RenderObject::SelectionInside; 83 else if (state == RenderObject::SelectionBoth) 84 state = RenderObject::SelectionNone; 85 } 86 87 // If there are ellipsis following, make sure their selection is updated. 88 if (m_truncation != cNoTruncation && root()->ellipsisBox()) { 89 EllipsisBox* ellipsis = root()->ellipsisBox(); 90 if (state != RenderObject::SelectionNone) { 91 int start, end; 92 selectionStartEnd(start, end); 93 // The ellipsis should be considered to be selected if the end of 94 // the selection is past the beginning of the truncation and the 95 // beginning of the selection is before or at the beginning of the 96 // truncation. 97 ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ? 98 RenderObject::SelectionInside : RenderObject::SelectionNone); 99 } else 100 ellipsis->setSelectionState(RenderObject::SelectionNone); 101 } 102 103 return state; 104 } 105 106 IntRect InlineTextBox::selectionRect(int tx, int ty, int startPos, int endPos) 107 { 108 int sPos = max(startPos - m_start, 0); 109 int ePos = min(endPos - m_start, (int)m_len); 110 111 if (sPos > ePos) 112 return IntRect(); 113 114 RenderText* textObj = textRenderer(); 115 int selTop = selectionTop(); 116 int selHeight = selectionHeight(); 117 const Font& f = textObj->style(m_firstLine)->font(); 118 119 IntRect r = enclosingIntRect(f.selectionRectForText(TextRun(textObj->text()->characters() + m_start, m_len, textObj->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride), 120 IntPoint(tx + m_x, ty + selTop), selHeight, sPos, ePos)); 121 if (r.x() > tx + m_x + m_width) 122 r.setWidth(0); 123 else if (r.right() - 1 > tx + m_x + m_width) 124 r.setWidth(tx + m_x + m_width - r.x()); 125 return r; 126 } 127 128 void InlineTextBox::deleteLine(RenderArena* arena) 129 { 130 toRenderText(renderer())->removeTextBox(this); 131 destroy(arena); 132 } 133 134 void InlineTextBox::extractLine() 135 { 136 if (m_extracted) 137 return; 138 139 toRenderText(renderer())->extractTextBox(this); 140 } 141 142 void InlineTextBox::attachLine() 143 { 144 if (!m_extracted) 145 return; 146 147 toRenderText(renderer())->attachTextBox(this); 148 } 149 150 int InlineTextBox::placeEllipsisBox(bool flowIsLTR, int visibleLeftEdge, int visibleRightEdge, int ellipsisWidth, bool& foundBox) 151 { 152 if (foundBox) { 153 m_truncation = cFullTruncation; 154 return -1; 155 } 156 157 // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates. 158 int ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth; 159 160 // Criteria for full truncation: 161 // LTR: the left edge of the ellipsis is to the left of our text run. 162 // RTL: the right edge of the ellipsis is to the right of our text run. 163 bool ltrFullTruncation = flowIsLTR && ellipsisX <= m_x; 164 bool rtlFullTruncation = !flowIsLTR && ellipsisX >= (m_x + m_width); 165 if (ltrFullTruncation || rtlFullTruncation) { 166 // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box. 167 m_truncation = cFullTruncation; 168 foundBox = true; 169 return -1; 170 } 171 172 bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < m_x + m_width); 173 bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > m_x); 174 if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) { 175 foundBox = true; 176 177 // The inline box may have different directionality than it's parent. Since truncation 178 // behavior depends both on both the parent and the inline block's directionality, we 179 // must keep track of these separately. 180 bool ltr = direction() == LTR; 181 if (ltr != flowIsLTR) { 182 // Width in pixels of the visible portion of the box, excluding the ellipsis. 183 int visibleBoxWidth = visibleRightEdge - visibleLeftEdge - ellipsisWidth; 184 ellipsisX = ltr ? m_x + visibleBoxWidth : m_x + m_width - visibleBoxWidth; 185 } 186 187 int offset = offsetForPosition(ellipsisX, false); 188 if (offset == 0) { 189 // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start 190 // and the ellipsis edge. 191 m_truncation = cFullTruncation; 192 return min(ellipsisX, m_x); 193 } 194 195 // Set the truncation index on the text run. 196 m_truncation = offset; 197 198 // If we got here that means that we were only partially truncated and we need to return the pixel offset at which 199 // to place the ellipsis. 200 int widthOfVisibleText = toRenderText(renderer())->width(m_start, offset, textPos(), m_firstLine); 201 202 // The ellipsis needs to be placed just after the last visible character. 203 // Where "after" is defined by the flow directionality, not the inline 204 // box directionality. 205 // e.g. In the case of an LTR inline box truncated in an RTL flow then we can 206 // have a situation such as |Hello| -> |...He| 207 if (flowIsLTR) 208 return m_x + widthOfVisibleText; 209 else 210 return (m_x + m_width) - widthOfVisibleText - ellipsisWidth; 211 } 212 return -1; 213 } 214 215 Color correctedTextColor(Color textColor, Color backgroundColor) 216 { 217 // Adjust the text color if it is too close to the background color, 218 // by darkening or lightening it to move it further away. 219 220 int d = differenceSquared(textColor, backgroundColor); 221 // semi-arbitrarily chose 65025 (255^2) value here after a few tests; 222 if (d > 65025) { 223 return textColor; 224 } 225 226 int distanceFromWhite = differenceSquared(textColor, Color::white); 227 int distanceFromBlack = differenceSquared(textColor, Color::black); 228 229 if (distanceFromWhite < distanceFromBlack) { 230 return textColor.dark(); 231 } 232 233 return textColor.light(); 234 } 235 236 void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness, ColorSpace colorSpace) 237 { 238 int mode = context->textDrawingMode(); 239 if (strokeThickness > 0) { 240 int newMode = mode | cTextStroke; 241 if (mode != newMode) { 242 context->setTextDrawingMode(newMode); 243 mode = newMode; 244 } 245 } 246 247 if (mode & cTextFill && (fillColor != context->fillColor() || colorSpace != context->fillColorSpace())) 248 context->setFillColor(fillColor, colorSpace); 249 250 if (mode & cTextStroke) { 251 if (strokeColor != context->strokeColor()) 252 context->setStrokeColor(strokeColor, colorSpace); 253 if (strokeThickness != context->strokeThickness()) 254 context->setStrokeThickness(strokeThickness); 255 } 256 } 257 258 bool InlineTextBox::isLineBreak() const 259 { 260 return renderer()->isBR() || (renderer()->style()->preserveNewline() && len() == 1 && (*textRenderer()->text())[start()] == '\n'); 261 } 262 263 bool InlineTextBox::nodeAtPoint(const HitTestRequest&, HitTestResult& result, int x, int y, int tx, int ty) 264 { 265 if (isLineBreak()) 266 return false; 267 268 IntRect rect(tx + m_x, ty + m_y, m_width, height()); 269 if (m_truncation != cFullTruncation && visibleToHitTesting() && rect.contains(x, y)) { 270 renderer()->updateHitTestResult(result, IntPoint(x - tx, y - ty)); 271 return true; 272 } 273 return false; 274 } 275 276 static void paintTextWithShadows(GraphicsContext* context, const Font& font, const TextRun& textRun, int startOffset, int endOffset, int truncationPoint, const IntPoint& textOrigin, int x, int y, int w, int h, ShadowData* shadow, bool stroked) 277 { 278 Color fillColor = context->fillColor(); 279 ColorSpace fillColorSpace = context->fillColorSpace(); 280 bool opaque = fillColor.alpha() == 255; 281 if (!opaque) 282 context->setFillColor(Color::black, fillColorSpace); 283 284 do { 285 IntSize extraOffset; 286 287 if (shadow) { 288 IntSize shadowOffset(shadow->x, shadow->y); 289 int shadowBlur = shadow->blur; 290 const Color& shadowColor = shadow->color; 291 292 if (shadow->next || stroked || !opaque) { 293 IntRect shadowRect(x, y, w, h); 294 shadowRect.inflate(shadowBlur); 295 shadowRect.move(shadowOffset); 296 context->save(); 297 context->clip(shadowRect); 298 299 extraOffset = IntSize(0, 2 * h + max(0, shadowOffset.height()) + shadowBlur); 300 shadowOffset -= extraOffset; 301 } 302 context->setShadow(shadowOffset, shadowBlur, shadowColor, fillColorSpace); 303 } else if (!opaque) 304 context->setFillColor(fillColor, fillColorSpace); 305 306 if (startOffset <= endOffset) 307 context->drawText(font, textRun, textOrigin + extraOffset, startOffset, endOffset); 308 else { 309 if (endOffset > 0) 310 context->drawText(font, textRun, textOrigin + extraOffset, 0, endOffset); 311 if (startOffset < truncationPoint) 312 context->drawText(font, textRun, textOrigin + extraOffset, startOffset, truncationPoint); 313 } 314 315 if (!shadow) 316 break; 317 318 if (shadow->next || stroked || !opaque) 319 context->restore(); 320 else 321 context->clearShadow(); 322 323 shadow = shadow->next; 324 } while (shadow || stroked || !opaque); 325 } 326 327 void InlineTextBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty) 328 { 329 if (isLineBreak() || !renderer()->shouldPaintWithinRoot(paintInfo) || renderer()->style()->visibility() != VISIBLE || 330 m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline) 331 return; 332 333 ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines); 334 335 // FIXME: Technically we're potentially incorporating other visual overflow that had nothing to do with us. 336 // Would it be simpler to just check our own shadow and stroke overflow by hand here? 337 int leftOverflow = parent()->x() - parent()->leftVisualOverflow(); 338 int rightOverflow = parent()->rightVisualOverflow() - (parent()->x() + parent()->width()); 339 int xPos = tx + m_x - leftOverflow; 340 int w = width() + leftOverflow + rightOverflow; 341 if (xPos >= paintInfo.rect.right() || xPos + w <= paintInfo.rect.x()) 342 return; 343 344 bool isPrinting = textRenderer()->document()->printing(); 345 346 // Determine whether or not we're selected. 347 bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone; 348 if (!haveSelection && paintInfo.phase == PaintPhaseSelection) 349 // When only painting the selection, don't bother to paint if there is none. 350 return; 351 352 if (m_truncation != cNoTruncation) { 353 TextDirection flowDirection = renderer()->containingBlock()->style()->direction(); 354 if (flowDirection != direction()) { 355 // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin 356 // at which we start drawing text. 357 // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is: 358 // |Hello|CBA| -> |...He|CBA| 359 // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing 360 // farther to the right. 361 // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the 362 // truncated string i.e. |Hello|CBA| -> |...lo|CBA| 363 int widthOfVisibleText = toRenderText(renderer())->width(m_start, m_truncation, textPos(), m_firstLine); 364 int widthOfHiddenText = m_width - widthOfVisibleText; 365 // FIXME: The hit testing logic also needs to take this translation int account. 366 tx += direction() == LTR ? widthOfHiddenText : -widthOfHiddenText; 367 } 368 } 369 370 GraphicsContext* context = paintInfo.context; 371 372 // Determine whether or not we have composition underlines to draw. 373 bool containsComposition = renderer()->node() && renderer()->document()->frame()->editor()->compositionNode() == renderer()->node(); 374 bool useCustomUnderlines = containsComposition && renderer()->document()->frame()->editor()->compositionUsesCustomUnderlines(); 375 376 // Set our font. 377 RenderStyle* styleToUse = renderer()->style(m_firstLine); 378 int d = styleToUse->textDecorationsInEffect(); 379 const Font& font = styleToUse->font(); 380 381 // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection 382 // and composition underlines. 383 if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) { 384 #if PLATFORM(MAC) 385 // Custom highlighters go behind everything else. 386 if (styleToUse->highlight() != nullAtom && !context->paintingDisabled()) 387 paintCustomHighlight(tx, ty, styleToUse->highlight()); 388 #endif 389 390 if (containsComposition && !useCustomUnderlines) 391 paintCompositionBackground(context, tx, ty, styleToUse, font, 392 renderer()->document()->frame()->editor()->compositionStart(), 393 renderer()->document()->frame()->editor()->compositionEnd()); 394 395 paintDocumentMarkers(context, tx, ty, styleToUse, font, true); 396 397 if (haveSelection && !useCustomUnderlines) 398 paintSelection(context, tx, ty, styleToUse, font); 399 } 400 401 // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only). 402 if (m_len <= 0) 403 return; 404 405 Color textFillColor; 406 Color textStrokeColor; 407 float textStrokeWidth = styleToUse->textStrokeWidth(); 408 ShadowData* textShadow = paintInfo.forceBlackText ? 0 : styleToUse->textShadow(); 409 410 if (paintInfo.forceBlackText) { 411 textFillColor = Color::black; 412 textStrokeColor = Color::black; 413 } else { 414 textFillColor = styleToUse->textFillColor(); 415 if (!textFillColor.isValid()) 416 textFillColor = styleToUse->color(); 417 418 // Make the text fill color legible against a white background 419 if (styleToUse->forceBackgroundsToWhite()) 420 textFillColor = correctedTextColor(textFillColor, Color::white); 421 422 textStrokeColor = styleToUse->textStrokeColor(); 423 if (!textStrokeColor.isValid()) 424 textStrokeColor = styleToUse->color(); 425 426 // Make the text stroke color legible against a white background 427 if (styleToUse->forceBackgroundsToWhite()) 428 textStrokeColor = correctedTextColor(textStrokeColor, Color::white); 429 } 430 431 bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection); 432 bool paintSelectedTextSeparately = false; 433 434 Color selectionFillColor = textFillColor; 435 Color selectionStrokeColor = textStrokeColor; 436 float selectionStrokeWidth = textStrokeWidth; 437 ShadowData* selectionShadow = textShadow; 438 if (haveSelection) { 439 // Check foreground color first. 440 Color foreground = paintInfo.forceBlackText ? Color::black : renderer()->selectionForegroundColor(); 441 if (foreground.isValid() && foreground != selectionFillColor) { 442 if (!paintSelectedTextOnly) 443 paintSelectedTextSeparately = true; 444 selectionFillColor = foreground; 445 } 446 447 if (RenderStyle* pseudoStyle = renderer()->getCachedPseudoStyle(SELECTION)) { 448 ShadowData* shadow = paintInfo.forceBlackText ? 0 : pseudoStyle->textShadow(); 449 if (shadow != selectionShadow) { 450 if (!paintSelectedTextOnly) 451 paintSelectedTextSeparately = true; 452 selectionShadow = shadow; 453 } 454 455 float strokeWidth = pseudoStyle->textStrokeWidth(); 456 if (strokeWidth != selectionStrokeWidth) { 457 if (!paintSelectedTextOnly) 458 paintSelectedTextSeparately = true; 459 selectionStrokeWidth = strokeWidth; 460 } 461 462 Color stroke = paintInfo.forceBlackText ? Color::black : pseudoStyle->textStrokeColor(); 463 if (!stroke.isValid()) 464 stroke = pseudoStyle->color(); 465 if (stroke != selectionStrokeColor) { 466 if (!paintSelectedTextOnly) 467 paintSelectedTextSeparately = true; 468 selectionStrokeColor = stroke; 469 } 470 } 471 } 472 473 int baseline = renderer()->style(m_firstLine)->font().ascent(); 474 IntPoint textOrigin(m_x + tx, m_y + ty + baseline); 475 TextRun textRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride || styleToUse->visuallyOrdered()); 476 477 int sPos = 0; 478 int ePos = 0; 479 if (paintSelectedTextOnly || paintSelectedTextSeparately) 480 selectionStartEnd(sPos, ePos); 481 482 int length = m_len; 483 if (m_truncation != cNoTruncation) { 484 sPos = min<int>(sPos, m_truncation); 485 ePos = min<int>(ePos, m_truncation); 486 length = m_truncation; 487 } 488 489 if (!paintSelectedTextOnly) { 490 // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side 491 // effect, so only when we know we're stroking, do a save/restore. 492 if (textStrokeWidth > 0) 493 context->save(); 494 495 updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace()); 496 if (!paintSelectedTextSeparately || ePos <= sPos) { 497 // FIXME: Truncate right-to-left text correctly. 498 paintTextWithShadows(context, font, textRun, 0, length, length, textOrigin, m_x + tx, m_y + ty, width(), height(), textShadow, textStrokeWidth > 0); 499 } else 500 paintTextWithShadows(context, font, textRun, ePos, sPos, length, textOrigin, m_x + tx, m_y + ty, width(), height(), textShadow, textStrokeWidth > 0); 501 502 if (textStrokeWidth > 0) 503 context->restore(); 504 } 505 506 if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) { 507 // paint only the text that is selected 508 if (selectionStrokeWidth > 0) 509 context->save(); 510 511 updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth, styleToUse->colorSpace()); 512 paintTextWithShadows(context, font, textRun, sPos, ePos, length, textOrigin, m_x + tx, m_y + ty, width(), height(), selectionShadow, selectionStrokeWidth > 0); 513 514 if (selectionStrokeWidth > 0) 515 context->restore(); 516 } 517 518 // Paint decorations 519 if (d != TDNONE && paintInfo.phase != PaintPhaseSelection && styleToUse->htmlHacks()) { 520 context->setStrokeColor(styleToUse->color(), styleToUse->colorSpace()); 521 paintDecoration(context, tx, ty, d, textShadow); 522 } 523 524 if (paintInfo.phase == PaintPhaseForeground) { 525 paintDocumentMarkers(context, tx, ty, styleToUse, font, false); 526 527 if (useCustomUnderlines) { 528 const Vector<CompositionUnderline>& underlines = renderer()->document()->frame()->editor()->customCompositionUnderlines(); 529 size_t numUnderlines = underlines.size(); 530 531 for (size_t index = 0; index < numUnderlines; ++index) { 532 const CompositionUnderline& underline = underlines[index]; 533 534 if (underline.endOffset <= start()) 535 // underline is completely before this run. This might be an underline that sits 536 // before the first run we draw, or underlines that were within runs we skipped 537 // due to truncation. 538 continue; 539 540 if (underline.startOffset <= end()) { 541 // underline intersects this run. Paint it. 542 paintCompositionUnderline(context, tx, ty, underline); 543 if (underline.endOffset > end() + 1) 544 // underline also runs into the next run. Bail now, no more marker advancement. 545 break; 546 } else 547 // underline is completely after this run, bail. A later run will paint it. 548 break; 549 } 550 } 551 } 552 } 553 554 void InlineTextBox::selectionStartEnd(int& sPos, int& ePos) 555 { 556 int startPos, endPos; 557 if (renderer()->selectionState() == RenderObject::SelectionInside) { 558 startPos = 0; 559 endPos = textRenderer()->textLength(); 560 } else { 561 textRenderer()->selectionStartEnd(startPos, endPos); 562 if (renderer()->selectionState() == RenderObject::SelectionStart) 563 endPos = textRenderer()->textLength(); 564 else if (renderer()->selectionState() == RenderObject::SelectionEnd) 565 startPos = 0; 566 } 567 568 sPos = max(startPos - m_start, 0); 569 ePos = min(endPos - m_start, (int)m_len); 570 } 571 572 void InlineTextBox::paintSelection(GraphicsContext* context, int tx, int ty, RenderStyle* style, const Font& font) 573 { 574 // See if we have a selection to paint at all. 575 int sPos, ePos; 576 selectionStartEnd(sPos, ePos); 577 if (sPos >= ePos) 578 return; 579 580 Color textColor = style->color(); 581 Color c = renderer()->selectionBackgroundColor(); 582 if (!c.isValid() || c.alpha() == 0) 583 return; 584 585 // If the text color ends up being the same as the selection background, invert the selection 586 // background. This should basically never happen, since the selection has transparency. 587 if (textColor == c) 588 c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue()); 589 590 context->save(); 591 updateGraphicsContext(context, c, c, 0, style->colorSpace()); // Don't draw text at all! 592 int y = selectionTop(); 593 int h = selectionHeight(); 594 // If the text is truncated, let the thing being painted in the truncation 595 // draw its own highlight. 596 int length = m_truncation != cNoTruncation ? m_truncation : m_len; 597 context->clip(IntRect(m_x + tx, y + ty, m_width, h)); 598 context->drawHighlightForText(font, TextRun(textRenderer()->text()->characters() + m_start, length, textRenderer()->allowTabs(), textPos(), m_toAdd, 599 direction() == RTL, m_dirOverride || style->visuallyOrdered()), 600 IntPoint(m_x + tx, y + ty), h, c, style->colorSpace(), sPos, ePos); 601 context->restore(); 602 } 603 604 void InlineTextBox::paintCompositionBackground(GraphicsContext* context, int tx, int ty, RenderStyle* style, const Font& font, int startPos, int endPos) 605 { 606 int offset = m_start; 607 int sPos = max(startPos - offset, 0); 608 int ePos = min(endPos - offset, (int)m_len); 609 610 if (sPos >= ePos) 611 return; 612 613 context->save(); 614 615 Color c = Color(225, 221, 85); 616 617 updateGraphicsContext(context, c, c, 0, style->colorSpace()); // Don't draw text at all! 618 619 int y = selectionTop(); 620 int h = selectionHeight(); 621 context->drawHighlightForText(font, TextRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, 622 direction() == RTL, m_dirOverride || style->visuallyOrdered()), 623 IntPoint(m_x + tx, y + ty), h, c, style->colorSpace(), sPos, ePos); 624 context->restore(); 625 } 626 627 #if PLATFORM(MAC) 628 629 void InlineTextBox::paintCustomHighlight(int tx, int ty, const AtomicString& type) 630 { 631 Frame* frame = renderer()->document()->frame(); 632 if (!frame) 633 return; 634 Page* page = frame->page(); 635 if (!page) 636 return; 637 638 RootInlineBox* r = root(); 639 FloatRect rootRect(tx + r->x(), ty + selectionTop(), r->width(), selectionHeight()); 640 FloatRect textRect(tx + x(), rootRect.y(), width(), rootRect.height()); 641 642 page->chrome()->client()->paintCustomHighlight(renderer()->node(), type, textRect, rootRect, true, false); 643 } 644 645 #endif 646 647 void InlineTextBox::paintDecoration(GraphicsContext* context, int tx, int ty, int deco, ShadowData* shadow) 648 { 649 tx += m_x; 650 ty += m_y; 651 652 if (m_truncation == cFullTruncation) 653 return; 654 655 int width = m_width; 656 if (m_truncation != cNoTruncation) { 657 width = toRenderText(renderer())->width(m_start, m_truncation, textPos(), m_firstLine); 658 if (direction() == RTL) 659 tx += (m_width - width); 660 } 661 662 // Get the text decoration colors. 663 Color underline, overline, linethrough; 664 renderer()->getTextDecorationColors(deco, underline, overline, linethrough, true); 665 666 // Use a special function for underlines to get the positioning exactly right. 667 bool isPrinting = textRenderer()->document()->printing(); 668 context->setStrokeThickness(1.0f); // FIXME: We should improve this rule and not always just assume 1. 669 670 bool linesAreOpaque = !isPrinting && (!(deco & UNDERLINE) || underline.alpha() == 255) && (!(deco & OVERLINE) || overline.alpha() == 255) && (!(deco & LINE_THROUGH) || linethrough.alpha() == 255); 671 672 int baseline = renderer()->style(m_firstLine)->font().ascent(); 673 674 bool setClip = false; 675 int extraOffset = 0; 676 if (!linesAreOpaque && shadow && shadow->next) { 677 context->save(); 678 IntRect clipRect(tx, ty, width, baseline + 2); 679 for (ShadowData* s = shadow; s; s = s->next) { 680 IntRect shadowRect(tx, ty, width, baseline + 2); 681 shadowRect.inflate(s->blur); 682 shadowRect.move(s->x, s->y); 683 clipRect.unite(shadowRect); 684 extraOffset = max(extraOffset, max(0, s->y) + s->blur); 685 } 686 context->save(); 687 context->clip(clipRect); 688 extraOffset += baseline + 2; 689 ty += extraOffset; 690 setClip = true; 691 } 692 693 ColorSpace colorSpace = renderer()->style()->colorSpace(); 694 bool setShadow = false; 695 696 do { 697 if (shadow) { 698 if (!shadow->next) { 699 // The last set of lines paints normally inside the clip. 700 ty -= extraOffset; 701 extraOffset = 0; 702 } 703 context->setShadow(IntSize(shadow->x, shadow->y - extraOffset), shadow->blur, shadow->color, colorSpace); 704 setShadow = true; 705 shadow = shadow->next; 706 } 707 708 if (deco & UNDERLINE) { 709 context->setStrokeColor(underline, colorSpace); 710 context->setStrokeStyle(SolidStroke); 711 // Leave one pixel of white between the baseline and the underline. 712 context->drawLineForText(IntPoint(tx, ty + baseline + 1), width, isPrinting); 713 } 714 if (deco & OVERLINE) { 715 context->setStrokeColor(overline, colorSpace); 716 context->setStrokeStyle(SolidStroke); 717 context->drawLineForText(IntPoint(tx, ty), width, isPrinting); 718 } 719 if (deco & LINE_THROUGH) { 720 context->setStrokeColor(linethrough, colorSpace); 721 context->setStrokeStyle(SolidStroke); 722 context->drawLineForText(IntPoint(tx, ty + 2 * baseline / 3), width, isPrinting); 723 } 724 } while (shadow); 725 726 if (setClip) 727 context->restore(); 728 else if (setShadow) 729 context->clearShadow(); 730 } 731 732 void InlineTextBox::paintSpellingOrGrammarMarker(GraphicsContext* pt, int tx, int ty, const DocumentMarker& marker, RenderStyle* style, const Font& font, bool grammar) 733 { 734 // Never print spelling/grammar markers (5327887) 735 if (textRenderer()->document()->printing()) 736 return; 737 738 if (m_truncation == cFullTruncation) 739 return; 740 741 int start = 0; // start of line to draw, relative to tx 742 int width = m_width; // how much line to draw 743 744 // Determine whether we need to measure text 745 bool markerSpansWholeBox = true; 746 if (m_start <= (int)marker.startOffset) 747 markerSpansWholeBox = false; 748 if ((end() + 1) != marker.endOffset) // end points at the last char, not past it 749 markerSpansWholeBox = false; 750 if (m_truncation != cNoTruncation) 751 markerSpansWholeBox = false; 752 753 if (!markerSpansWholeBox || grammar) { 754 int startPosition = max<int>(marker.startOffset - m_start, 0); 755 int endPosition = min<int>(marker.endOffset - m_start, m_len); 756 757 if (m_truncation != cNoTruncation) 758 endPosition = min<int>(endPosition, m_truncation); 759 760 // Calculate start & width 761 IntPoint startPoint(tx + m_x, ty + selectionTop()); 762 TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride || style->visuallyOrdered()); 763 int h = selectionHeight(); 764 765 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, h, startPosition, endPosition)); 766 start = markerRect.x() - startPoint.x(); 767 width = markerRect.width(); 768 769 // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to 770 // display a toolTip. We don't do this for misspelling markers. 771 if (grammar) { 772 markerRect.move(-tx, -ty); 773 markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); 774 renderer()->document()->setRenderedRectForMarker(renderer()->node(), marker, markerRect); 775 } 776 } 777 778 // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to 779 // make sure to fit within those bounds. This means the top pixel(s) of the underline will overlap the 780 // bottom pixel(s) of the glyphs in smaller font sizes. The alternatives are to increase the line spacing (bad!!) 781 // or decrease the underline thickness. The overlap is actually the most useful, and matches what AppKit does. 782 // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so 783 // we pin to two pixels under the baseline. 784 int lineThickness = cMisspellingLineThickness; 785 int baseline = renderer()->style(m_firstLine)->font().ascent(); 786 int descent = height() - baseline; 787 int underlineOffset; 788 if (descent <= (2 + lineThickness)) { 789 // Place the underline at the very bottom of the text in small/medium fonts. 790 underlineOffset = height() - lineThickness; 791 } else { 792 // In larger fonts, though, place the underline up near the baseline to prevent a big gap. 793 underlineOffset = baseline + 2; 794 } 795 pt->drawLineForMisspellingOrBadGrammar(IntPoint(tx + m_x + start, ty + m_y + underlineOffset), width, grammar); 796 } 797 798 void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, int tx, int ty, const DocumentMarker& marker, RenderStyle* style, const Font& font) 799 { 800 // Use same y positioning and height as for selection, so that when the selection and this highlight are on 801 // the same word there are no pieces sticking out. 802 int y = selectionTop(); 803 int h = selectionHeight(); 804 805 int sPos = max(marker.startOffset - m_start, (unsigned)0); 806 int ePos = min(marker.endOffset - m_start, (unsigned)m_len); 807 TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride || style->visuallyOrdered()); 808 809 // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates. 810 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, IntPoint(m_x, y), h, sPos, ePos)); 811 markerRect = root()->block()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); 812 renderer()->document()->setRenderedRectForMarker(renderer()->node(), marker, markerRect); 813 814 // Optionally highlight the text 815 if (renderer()->document()->frame()->markedTextMatchesAreHighlighted()) { 816 Color color = marker.activeMatch ? 817 renderer()->theme()->platformActiveTextSearchHighlightColor() : 818 renderer()->theme()->platformInactiveTextSearchHighlightColor(); 819 pt->save(); 820 updateGraphicsContext(pt, color, color, 0, style->colorSpace()); // Don't draw text at all! 821 pt->clip(IntRect(tx + m_x, ty + y, m_width, h)); 822 pt->drawHighlightForText(font, run, IntPoint(m_x + tx, y + ty), h, color, style->colorSpace(), sPos, ePos); 823 pt->restore(); 824 } 825 } 826 827 void InlineTextBox::computeRectForReplacementMarker(int /*tx*/, int /*ty*/, const DocumentMarker& marker, RenderStyle* style, const Font& font) 828 { 829 // Replacement markers are not actually drawn, but their rects need to be computed for hit testing. 830 int y = selectionTop(); 831 int h = selectionHeight(); 832 833 int sPos = max(marker.startOffset - m_start, (unsigned)0); 834 int ePos = min(marker.endOffset - m_start, (unsigned)m_len); 835 TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride || style->visuallyOrdered()); 836 IntPoint startPoint = IntPoint(m_x, y); 837 838 // Compute and store the rect associated with this marker. 839 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, h, sPos, ePos)); 840 markerRect = root()->block()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); 841 renderer()->document()->setRenderedRectForMarker(renderer()->node(), marker, markerRect); 842 } 843 844 void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, int tx, int ty, RenderStyle* style, const Font& font, bool background) 845 { 846 if (!renderer()->node()) 847 return; 848 849 Vector<DocumentMarker> markers = renderer()->document()->markersForNode(renderer()->node()); 850 Vector<DocumentMarker>::iterator markerIt = markers.begin(); 851 852 // Give any document markers that touch this run a chance to draw before the text has been drawn. 853 // Note end() points at the last char, not one past it like endOffset and ranges do. 854 for ( ; markerIt != markers.end(); markerIt++) { 855 const DocumentMarker& marker = *markerIt; 856 857 // Paint either the background markers or the foreground markers, but not both 858 switch (marker.type) { 859 case DocumentMarker::Grammar: 860 case DocumentMarker::Spelling: 861 case DocumentMarker::Replacement: 862 if (background) 863 continue; 864 break; 865 866 case DocumentMarker::TextMatch: 867 if (!background) 868 continue; 869 break; 870 871 default: 872 ASSERT_NOT_REACHED(); 873 } 874 875 if (marker.endOffset <= start()) 876 // marker is completely before this run. This might be a marker that sits before the 877 // first run we draw, or markers that were within runs we skipped due to truncation. 878 continue; 879 880 if (marker.startOffset > end()) 881 // marker is completely after this run, bail. A later run will paint it. 882 break; 883 884 // marker intersects this run. Paint it. 885 switch (marker.type) { 886 case DocumentMarker::Spelling: 887 paintSpellingOrGrammarMarker(pt, tx, ty, marker, style, font, false); 888 break; 889 case DocumentMarker::Grammar: 890 paintSpellingOrGrammarMarker(pt, tx, ty, marker, style, font, true); 891 break; 892 case DocumentMarker::TextMatch: 893 paintTextMatchMarker(pt, tx, ty, marker, style, font); 894 break; 895 case DocumentMarker::Replacement: 896 computeRectForReplacementMarker(tx, ty, marker, style, font); 897 break; 898 default: 899 ASSERT_NOT_REACHED(); 900 } 901 902 } 903 } 904 905 906 void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, int tx, int ty, const CompositionUnderline& underline) 907 { 908 tx += m_x; 909 ty += m_y; 910 911 if (m_truncation == cFullTruncation) 912 return; 913 914 int start = 0; // start of line to draw, relative to tx 915 int width = m_width; // how much line to draw 916 bool useWholeWidth = true; 917 unsigned paintStart = m_start; 918 unsigned paintEnd = end() + 1; // end points at the last char, not past it 919 if (paintStart <= underline.startOffset) { 920 paintStart = underline.startOffset; 921 useWholeWidth = false; 922 start = toRenderText(renderer())->width(m_start, paintStart - m_start, textPos(), m_firstLine); 923 } 924 if (paintEnd != underline.endOffset) { // end points at the last char, not past it 925 paintEnd = min(paintEnd, (unsigned)underline.endOffset); 926 useWholeWidth = false; 927 } 928 if (m_truncation != cNoTruncation) { 929 paintEnd = min(paintEnd, (unsigned)m_start + m_truncation); 930 useWholeWidth = false; 931 } 932 if (!useWholeWidth) { 933 width = toRenderText(renderer())->width(paintStart, paintEnd - paintStart, textPos() + start, m_firstLine); 934 } 935 936 // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline. 937 // All other marked text underlines are 1px thick. 938 // If there's not enough space the underline will touch or overlap characters. 939 int lineThickness = 1; 940 int baseline = renderer()->style(m_firstLine)->font().ascent(); 941 if (underline.thick && height() - baseline >= 2) 942 lineThickness = 2; 943 944 // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those. 945 // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too. 946 start += 1; 947 width -= 2; 948 949 ctx->setStrokeColor(underline.color, renderer()->style()->colorSpace()); 950 ctx->setStrokeThickness(lineThickness); 951 ctx->drawLineForText(IntPoint(tx + start, ty + height() - lineThickness), width, textRenderer()->document()->printing()); 952 } 953 954 int InlineTextBox::caretMinOffset() const 955 { 956 return m_start; 957 } 958 959 int InlineTextBox::caretMaxOffset() const 960 { 961 return m_start + m_len; 962 } 963 964 unsigned InlineTextBox::caretMaxRenderedOffset() const 965 { 966 return m_start + m_len; 967 } 968 969 int InlineTextBox::textPos() const 970 { 971 if (x() == 0) 972 return 0; 973 974 RenderBlock* blockElement = renderer()->containingBlock(); 975 return direction() == RTL ? x() - blockElement->borderRight() - blockElement->paddingRight() 976 : x() - blockElement->borderLeft() - blockElement->paddingLeft(); 977 } 978 979 int InlineTextBox::offsetForPosition(int _x, bool includePartialGlyphs) const 980 { 981 if (isLineBreak()) 982 return 0; 983 984 RenderText* text = toRenderText(renderer()); 985 RenderStyle* style = text->style(m_firstLine); 986 const Font* f = &style->font(); 987 return f->offsetForPosition(TextRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride || style->visuallyOrdered()), 988 _x - m_x, includePartialGlyphs); 989 } 990 991 int InlineTextBox::positionForOffset(int offset) const 992 { 993 ASSERT(offset >= m_start); 994 ASSERT(offset <= m_start + m_len); 995 996 if (isLineBreak()) 997 return m_x; 998 999 RenderText* text = toRenderText(renderer()); 1000 const Font& f = text->style(m_firstLine)->font(); 1001 int from = direction() == RTL ? offset - m_start : 0; 1002 int to = direction() == RTL ? m_len : offset - m_start; 1003 // FIXME: Do we need to add rightBearing here? 1004 return enclosingIntRect(f.selectionRectForText(TextRun(text->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride), 1005 IntPoint(m_x, 0), 0, from, to)).right(); 1006 } 1007 1008 bool InlineTextBox::containsCaretOffset(int offset) const 1009 { 1010 // Offsets before the box are never "in". 1011 if (offset < m_start) 1012 return false; 1013 1014 int pastEnd = m_start + m_len; 1015 1016 // Offsets inside the box (not at either edge) are always "in". 1017 if (offset < pastEnd) 1018 return true; 1019 1020 // Offsets outside the box are always "out". 1021 if (offset > pastEnd) 1022 return false; 1023 1024 // Offsets at the end are "out" for line breaks (they are on the next line). 1025 if (isLineBreak()) 1026 return false; 1027 1028 // Offsets at the end are "in" for normal boxes (but the caller has to check affinity). 1029 return true; 1030 } 1031 1032 typedef HashMap<InlineTextBox*, Vector<const SimpleFontData*> > FallbackFontsMap; 1033 static FallbackFontsMap* gFallbackFontsMap; 1034 1035 void InlineTextBox::setFallbackFonts(const HashSet<const SimpleFontData*>& fallbackFonts) 1036 { 1037 if (!gFallbackFontsMap) 1038 gFallbackFontsMap = new FallbackFontsMap; 1039 1040 FallbackFontsMap::iterator it = gFallbackFontsMap->set(this, Vector<const SimpleFontData*>()).first; 1041 ASSERT(it->second.isEmpty()); 1042 copyToVector(fallbackFonts, it->second); 1043 } 1044 1045 void InlineTextBox::takeFallbackFonts(Vector<const SimpleFontData*>& fallbackFonts) 1046 { 1047 if (!gFallbackFontsMap) 1048 return; 1049 1050 FallbackFontsMap::iterator it = gFallbackFontsMap->find(this); 1051 if (it == gFallbackFontsMap->end()) 1052 return; 1053 1054 fallbackFonts.swap(it->second); 1055 gFallbackFontsMap->remove(it); 1056 } 1057 1058 } // namespace WebCore 1059