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 Apple Inc. All rights reserved.
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.
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.
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.
24 #include "InlineTextBox.h"
27 #include "ChromeClient.h"
30 #include "EllipsisBox.h"
32 #include "GraphicsContext.h"
33 #include "HitTestResult.h"
35 #include "RenderArena.h"
36 #include "RenderBlock.h"
37 #include "RenderTheme.h"
39 #include "break_lines.h"
40 #include <wtf/AlwaysInline.h>
46 int InlineTextBox::baselinePosition(FontBaseline baselineType) const
48 if (!isText() || !parent())
50 return parent()->baselinePosition(baselineType);
53 int InlineTextBox::lineHeight() const
55 if (!isText() || !parent())
57 if (m_renderer->isBR())
58 return toRenderBR(m_renderer)->lineHeight(m_firstLine);
59 return parent()->lineHeight();
62 int InlineTextBox::selectionTop()
64 return root()->selectionTop();
67 int InlineTextBox::selectionBottom()
69 return root()->selectionBottom();
72 int InlineTextBox::selectionHeight()
74 return root()->selectionHeight();
77 bool InlineTextBox::isSelected(int startPos, int endPos) const
79 int sPos = max(startPos - m_start, 0);
80 int ePos = min(endPos - m_start, (int)m_len);
84 RenderObject::SelectionState InlineTextBox::selectionState()
86 RenderObject::SelectionState state = renderer()->selectionState();
87 if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
89 renderer()->selectionStartEnd(startPos, endPos);
90 // The position after a hard line break is considered to be past its end.
91 int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0);
93 bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
94 bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
96 state = RenderObject::SelectionBoth;
98 state = RenderObject::SelectionStart;
100 state = RenderObject::SelectionEnd;
101 else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
102 (state == RenderObject::SelectionStart || endPos > lastSelectable))
103 state = RenderObject::SelectionInside;
104 else if (state == RenderObject::SelectionBoth)
105 state = RenderObject::SelectionNone;
108 // If there are ellipsis following, make sure their selection is updated.
109 if (m_truncation != cNoTruncation && root()->ellipsisBox()) {
110 EllipsisBox* ellipsis = root()->ellipsisBox();
111 if (state != RenderObject::SelectionNone) {
113 selectionStartEnd(start, end);
114 // The ellipsis should be considered to be selected if the end of
115 // the selection is past the beginning of the truncation and the
116 // beginning of the selection is before or at the beginning of the
118 ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ?
119 RenderObject::SelectionInside : RenderObject::SelectionNone);
121 ellipsis->setSelectionState(RenderObject::SelectionNone);
127 typedef Vector<UChar, 256> BufferForAppendingHyphen;
129 static void adjustCharactersAndLengthForHyphen(BufferForAppendingHyphen& charactersWithHyphen, RenderStyle* style, const UChar*& characters, int& length)
131 const AtomicString& hyphenString = style->hyphenString();
132 charactersWithHyphen.reserveCapacity(length + hyphenString.length());
133 charactersWithHyphen.append(characters, length);
134 charactersWithHyphen.append(hyphenString.characters(), hyphenString.length());
135 characters = charactersWithHyphen.data();
136 length += hyphenString.length();
139 IntRect InlineTextBox::selectionRect(int tx, int ty, int startPos, int endPos)
141 int sPos = max(startPos - m_start, 0);
142 int ePos = min(endPos - m_start, (int)m_len);
147 RenderText* textObj = textRenderer();
148 int selTop = selectionTop();
149 int selHeight = selectionHeight();
150 RenderStyle* styleToUse = textObj->style(m_firstLine);
151 const Font& f = styleToUse->font();
153 const UChar* characters = textObj->text()->characters() + m_start;
155 BufferForAppendingHyphen charactersWithHyphen;
156 if (ePos == len && hasHyphen()) {
157 adjustCharactersAndLengthForHyphen(charactersWithHyphen, styleToUse, characters, len);
161 IntRect r = enclosingIntRect(f.selectionRectForText(TextRun(characters, len, textObj->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride),
162 IntPoint(), selHeight, sPos, ePos));
164 int logicalWidth = r.width();
165 if (r.x() > m_logicalWidth)
167 else if (r.right() > m_logicalWidth)
168 logicalWidth = m_logicalWidth - r.x();
170 IntPoint topPoint = isHorizontal() ? IntPoint(tx + m_x + r.x(), ty + selTop) : IntPoint(tx + selTop, ty + m_y + r.x());
171 int width = isHorizontal() ? logicalWidth : selHeight;
172 int height = isHorizontal() ? selHeight : logicalWidth;
174 return IntRect(topPoint, IntSize(width, height));
177 void InlineTextBox::deleteLine(RenderArena* arena)
179 toRenderText(renderer())->removeTextBox(this);
183 void InlineTextBox::extractLine()
188 toRenderText(renderer())->extractTextBox(this);
191 void InlineTextBox::attachLine()
196 toRenderText(renderer())->attachTextBox(this);
199 int InlineTextBox::placeEllipsisBox(bool flowIsLTR, int visibleLeftEdge, int visibleRightEdge, int ellipsisWidth, bool& foundBox)
202 m_truncation = cFullTruncation;
206 // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
207 int ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
209 // Criteria for full truncation:
210 // LTR: the left edge of the ellipsis is to the left of our text run.
211 // RTL: the right edge of the ellipsis is to the right of our text run.
212 bool ltrFullTruncation = flowIsLTR && ellipsisX <= m_x;
213 bool rtlFullTruncation = !flowIsLTR && ellipsisX >= (m_x + m_logicalWidth);
214 if (ltrFullTruncation || rtlFullTruncation) {
215 // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
216 m_truncation = cFullTruncation;
221 bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < m_x + m_logicalWidth);
222 bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > m_x);
223 if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
226 // The inline box may have different directionality than it's parent. Since truncation
227 // behavior depends both on both the parent and the inline block's directionality, we
228 // must keep track of these separately.
229 bool ltr = isLeftToRightDirection();
230 if (ltr != flowIsLTR) {
231 // Width in pixels of the visible portion of the box, excluding the ellipsis.
232 int visibleBoxWidth = visibleRightEdge - visibleLeftEdge - ellipsisWidth;
233 ellipsisX = ltr ? m_x + visibleBoxWidth : m_x + m_logicalWidth - visibleBoxWidth;
236 int offset = offsetForPosition(ellipsisX, false);
238 // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start
239 // and the ellipsis edge.
240 m_truncation = cFullTruncation;
241 return min(ellipsisX, m_x);
244 // Set the truncation index on the text run.
245 m_truncation = offset;
247 // If we got here that means that we were only partially truncated and we need to return the pixel offset at which
248 // to place the ellipsis.
249 int widthOfVisibleText = toRenderText(renderer())->width(m_start, offset, textPos(), m_firstLine);
251 // The ellipsis needs to be placed just after the last visible character.
252 // Where "after" is defined by the flow directionality, not the inline
253 // box directionality.
254 // e.g. In the case of an LTR inline box truncated in an RTL flow then we can
255 // have a situation such as |Hello| -> |...He|
257 return m_x + widthOfVisibleText;
259 return (m_x + m_logicalWidth) - widthOfVisibleText - ellipsisWidth;
264 Color correctedTextColor(Color textColor, Color backgroundColor)
266 // Adjust the text color if it is too close to the background color,
267 // by darkening or lightening it to move it further away.
269 int d = differenceSquared(textColor, backgroundColor);
270 // semi-arbitrarily chose 65025 (255^2) value here after a few tests;
275 int distanceFromWhite = differenceSquared(textColor, Color::white);
276 int distanceFromBlack = differenceSquared(textColor, Color::black);
278 if (distanceFromWhite < distanceFromBlack) {
279 return textColor.dark();
282 return textColor.light();
285 void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness, ColorSpace colorSpace)
287 int mode = context->textDrawingMode();
288 if (strokeThickness > 0) {
289 int newMode = mode | cTextStroke;
290 if (mode != newMode) {
291 context->setTextDrawingMode(newMode);
296 if (mode & cTextFill && (fillColor != context->fillColor() || colorSpace != context->fillColorSpace()))
297 context->setFillColor(fillColor, colorSpace);
299 if (mode & cTextStroke) {
300 if (strokeColor != context->strokeColor())
301 context->setStrokeColor(strokeColor, colorSpace);
302 if (strokeThickness != context->strokeThickness())
303 context->setStrokeThickness(strokeThickness);
307 bool InlineTextBox::isLineBreak() const
309 return renderer()->isBR() || (renderer()->style()->preserveNewline() && len() == 1 && (*textRenderer()->text())[start()] == '\n');
312 bool InlineTextBox::nodeAtPoint(const HitTestRequest&, HitTestResult& result, int x, int y, int tx, int ty)
317 IntPoint boxOrigin = locationIncludingFlipping();
318 boxOrigin.move(tx, ty);
319 IntRect rect(boxOrigin, IntSize(width(), height()));
320 if (m_truncation != cFullTruncation && visibleToHitTesting() && rect.intersects(result.rectForPoint(x, y))) {
321 renderer()->updateHitTestResult(result, flipForWritingMode(IntPoint(x - tx, y - ty)));
322 if (!result.addNodeToRectBasedTestResult(renderer()->node(), x, y, rect))
328 FloatSize InlineTextBox::applyShadowToGraphicsContext(GraphicsContext* context, const ShadowData* shadow, const FloatRect& textRect, bool stroked, bool opaque, bool horizontal)
333 FloatSize extraOffset;
334 int shadowX = horizontal ? shadow->x() : shadow->y();
335 int shadowY = horizontal ? shadow->y() : -shadow->x();
336 FloatSize shadowOffset(shadowX, shadowY);
337 int shadowBlur = shadow->blur();
338 const Color& shadowColor = shadow->color();
340 if (shadow->next() || stroked || !opaque) {
341 FloatRect shadowRect(textRect);
342 shadowRect.inflate(shadowBlur);
343 shadowRect.move(shadowOffset);
345 context->clip(shadowRect);
347 extraOffset = FloatSize(0, 2 * textRect.height() + max(0.0f, shadowOffset.height()) + shadowBlur);
348 shadowOffset -= extraOffset;
351 context->setShadow(shadowOffset, shadowBlur, shadowColor, context->fillColorSpace());
355 static void paintTextWithShadows(GraphicsContext* context, const Font& font, const TextRun& textRun, int startOffset, int endOffset, int truncationPoint, const IntPoint& textOrigin,
356 const IntRect& boxRect, const ShadowData* shadow, bool stroked, bool horizontal)
358 Color fillColor = context->fillColor();
359 ColorSpace fillColorSpace = context->fillColorSpace();
360 bool opaque = fillColor.alpha() == 255;
362 context->setFillColor(Color::black, fillColorSpace);
367 extraOffset = roundedIntSize(InlineTextBox::applyShadowToGraphicsContext(context, shadow, boxRect, stroked, opaque, horizontal));
369 context->setFillColor(fillColor, fillColorSpace);
371 if (startOffset <= endOffset)
372 context->drawText(font, textRun, textOrigin + extraOffset, startOffset, endOffset);
375 context->drawText(font, textRun, textOrigin + extraOffset, 0, endOffset);
376 if (startOffset < truncationPoint)
377 context->drawText(font, textRun, textOrigin + extraOffset, startOffset, truncationPoint);
383 if (shadow->next() || stroked || !opaque)
386 context->clearShadow();
388 shadow = shadow->next();
389 } while (shadow || stroked || !opaque);
392 void InlineTextBox::paint(PaintInfo& paintInfo, int tx, int ty)
394 if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer()->style()->visibility() != VISIBLE ||
395 m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len)
398 ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
400 // FIXME: Technically we're potentially incorporating other visual overflow that had nothing to do with us.
401 // Would it be simpler to just check our own shadow and stroke overflow by hand here?
402 int logicalLeftOverflow = parent()->logicalLeft() - parent()->logicalLeftVisualOverflow();
403 int logicalRightOverflow = parent()->logicalRightVisualOverflow() - (parent()->logicalLeft() + parent()->logicalWidth());
404 int logicalStart = logicalLeft() - logicalLeftOverflow + (isHorizontal() ? tx : ty);
405 int logicalExtent = logicalWidth() + logicalLeftOverflow + logicalRightOverflow;
407 int paintEnd = isHorizontal() ? paintInfo.rect.right() : paintInfo.rect.bottom();
408 int paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
410 if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
413 bool isPrinting = textRenderer()->document()->printing();
415 // Determine whether or not we're selected.
416 bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
417 if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
418 // When only painting the selection, don't bother to paint if there is none.
421 if (m_truncation != cNoTruncation) {
422 if (renderer()->containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) {
423 // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
424 // at which we start drawing text.
425 // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
426 // |Hello|CBA| -> |...He|CBA|
427 // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
428 // farther to the right.
429 // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
430 // truncated string i.e. |Hello|CBA| -> |...lo|CBA|
431 int widthOfVisibleText = toRenderText(renderer())->width(m_start, m_truncation, textPos(), m_firstLine);
432 int widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
433 // FIXME: The hit testing logic also needs to take this translation int account.
435 tx += isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText;
437 ty += isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText;
441 GraphicsContext* context = paintInfo.context;
443 RenderStyle* styleToUse = renderer()->style(m_firstLine);
445 ty -= styleToUse->isHorizontalWritingMode() ? 0 : logicalHeight();
447 IntPoint boxOrigin = locationIncludingFlipping();
448 boxOrigin.move(tx, ty);
449 IntRect boxRect(boxOrigin, IntSize(logicalWidth(), logicalHeight()));
450 IntPoint textOrigin = IntPoint(boxOrigin.x(), boxOrigin.y() + styleToUse->font().ascent());
452 if (!isHorizontal()) {
454 context->translate(boxRect.x(), boxRect.bottom());
455 context->rotate(static_cast<float>(deg2rad(90.)));
456 context->translate(-boxRect.x(), -boxRect.bottom());
460 // Determine whether or not we have composition underlines to draw.
461 bool containsComposition = renderer()->node() && renderer()->frame()->editor()->compositionNode() == renderer()->node();
462 bool useCustomUnderlines = containsComposition && renderer()->frame()->editor()->compositionUsesCustomUnderlines();
465 int d = styleToUse->textDecorationsInEffect();
466 const Font& font = styleToUse->font();
468 // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
469 // and composition underlines.
470 if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
472 // Custom highlighters go behind everything else.
473 if (styleToUse->highlight() != nullAtom && !context->paintingDisabled())
474 paintCustomHighlight(tx, ty, styleToUse->highlight());
477 if (containsComposition && !useCustomUnderlines)
478 paintCompositionBackground(context, boxOrigin, styleToUse, font,
479 renderer()->frame()->editor()->compositionStart(),
480 renderer()->frame()->editor()->compositionEnd());
482 paintDocumentMarkers(context, boxOrigin, styleToUse, font, true);
484 if (haveSelection && !useCustomUnderlines)
485 paintSelection(context, boxOrigin, styleToUse, font);
488 // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
490 Color textStrokeColor;
491 float textStrokeWidth = styleToUse->textStrokeWidth();
492 const ShadowData* textShadow = paintInfo.forceBlackText ? 0 : styleToUse->textShadow();
494 if (paintInfo.forceBlackText) {
495 textFillColor = Color::black;
496 textStrokeColor = Color::black;
498 textFillColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextFillColor);
500 // Make the text fill color legible against a white background
501 if (styleToUse->forceBackgroundsToWhite())
502 textFillColor = correctedTextColor(textFillColor, Color::white);
504 textStrokeColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
506 // Make the text stroke color legible against a white background
507 if (styleToUse->forceBackgroundsToWhite())
508 textStrokeColor = correctedTextColor(textStrokeColor, Color::white);
511 bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection);
512 bool paintSelectedTextSeparately = false;
514 Color selectionFillColor = textFillColor;
515 Color selectionStrokeColor = textStrokeColor;
516 float selectionStrokeWidth = textStrokeWidth;
517 const ShadowData* selectionShadow = textShadow;
519 // Check foreground color first.
520 Color foreground = paintInfo.forceBlackText ? Color::black : renderer()->selectionForegroundColor();
521 if (foreground.isValid() && foreground != selectionFillColor) {
522 if (!paintSelectedTextOnly)
523 paintSelectedTextSeparately = true;
524 selectionFillColor = foreground;
527 if (RenderStyle* pseudoStyle = renderer()->getCachedPseudoStyle(SELECTION)) {
528 const ShadowData* shadow = paintInfo.forceBlackText ? 0 : pseudoStyle->textShadow();
529 if (shadow != selectionShadow) {
530 if (!paintSelectedTextOnly)
531 paintSelectedTextSeparately = true;
532 selectionShadow = shadow;
535 float strokeWidth = pseudoStyle->textStrokeWidth();
536 if (strokeWidth != selectionStrokeWidth) {
537 if (!paintSelectedTextOnly)
538 paintSelectedTextSeparately = true;
539 selectionStrokeWidth = strokeWidth;
542 Color stroke = paintInfo.forceBlackText ? Color::black : pseudoStyle->visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
543 if (stroke != selectionStrokeColor) {
544 if (!paintSelectedTextOnly)
545 paintSelectedTextSeparately = true;
546 selectionStrokeColor = stroke;
551 const UChar* characters = textRenderer()->text()->characters() + m_start;
553 BufferForAppendingHyphen charactersWithHyphen;
555 adjustCharactersAndLengthForHyphen(charactersWithHyphen, styleToUse, characters, length);
557 TextRun textRun(characters, length, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || styleToUse->visuallyOrdered());
561 if (paintSelectedTextOnly || paintSelectedTextSeparately)
562 selectionStartEnd(sPos, ePos);
564 if (m_truncation != cNoTruncation) {
565 sPos = min<int>(sPos, m_truncation);
566 ePos = min<int>(ePos, m_truncation);
567 length = m_truncation;
570 if (!paintSelectedTextOnly) {
571 // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side
572 // effect, so only when we know we're stroking, do a save/restore.
573 if (textStrokeWidth > 0)
576 updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
577 if (!paintSelectedTextSeparately || ePos <= sPos) {
578 // FIXME: Truncate right-to-left text correctly.
579 paintTextWithShadows(context, font, textRun, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
581 paintTextWithShadows(context, font, textRun, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
583 if (textStrokeWidth > 0)
587 if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) {
588 // paint only the text that is selected
589 if (selectionStrokeWidth > 0)
592 updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth, styleToUse->colorSpace());
593 paintTextWithShadows(context, font, textRun, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
595 if (selectionStrokeWidth > 0)
600 if (d != TDNONE && paintInfo.phase != PaintPhaseSelection) {
601 updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
602 paintDecoration(context, boxOrigin, d, textShadow);
605 if (paintInfo.phase == PaintPhaseForeground) {
606 paintDocumentMarkers(context, boxOrigin, styleToUse, font, false);
608 if (useCustomUnderlines) {
609 const Vector<CompositionUnderline>& underlines = renderer()->frame()->editor()->customCompositionUnderlines();
610 size_t numUnderlines = underlines.size();
612 for (size_t index = 0; index < numUnderlines; ++index) {
613 const CompositionUnderline& underline = underlines[index];
615 if (underline.endOffset <= start())
616 // underline is completely before this run. This might be an underline that sits
617 // before the first run we draw, or underlines that were within runs we skipped
618 // due to truncation.
621 if (underline.startOffset <= end()) {
622 // underline intersects this run. Paint it.
623 paintCompositionUnderline(context, boxOrigin, underline);
624 if (underline.endOffset > end() + 1)
625 // underline also runs into the next run. Bail now, no more marker advancement.
628 // underline is completely after this run, bail. A later run will paint it.
638 void InlineTextBox::selectionStartEnd(int& sPos, int& ePos)
640 int startPos, endPos;
641 if (renderer()->selectionState() == RenderObject::SelectionInside) {
643 endPos = textRenderer()->textLength();
645 textRenderer()->selectionStartEnd(startPos, endPos);
646 if (renderer()->selectionState() == RenderObject::SelectionStart)
647 endPos = textRenderer()->textLength();
648 else if (renderer()->selectionState() == RenderObject::SelectionEnd)
652 sPos = max(startPos - m_start, 0);
653 ePos = min(endPos - m_start, (int)m_len);
656 void InlineTextBox::paintSelection(GraphicsContext* context, const IntPoint& boxOrigin, RenderStyle* style, const Font& font)
658 // See if we have a selection to paint at all.
660 selectionStartEnd(sPos, ePos);
664 Color textColor = style->visitedDependentColor(CSSPropertyColor);
665 Color c = renderer()->selectionBackgroundColor();
666 if (!c.isValid() || c.alpha() == 0)
669 // If the text color ends up being the same as the selection background, invert the selection
670 // background. This should basically never happen, since the selection has transparency.
672 c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
675 updateGraphicsContext(context, c, c, 0, style->colorSpace()); // Don't draw text at all!
677 // If the text is truncated, let the thing being painted in the truncation
678 // draw its own highlight.
679 int length = m_truncation != cNoTruncation ? m_truncation : m_len;
680 const UChar* characters = textRenderer()->text()->characters() + m_start;
682 BufferForAppendingHyphen charactersWithHyphen;
683 if (ePos == length && hasHyphen()) {
684 adjustCharactersAndLengthForHyphen(charactersWithHyphen, style, characters, length);
688 int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
689 int selHeight = selectionHeight();
690 IntPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
691 context->clip(IntRect(localOrigin, IntSize(m_logicalWidth, selHeight)));
692 context->drawHighlightForText(font, TextRun(characters, length, textRenderer()->allowTabs(), textPos(), m_toAdd,
693 !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()),
694 localOrigin, selHeight, c, style->colorSpace(), sPos, ePos);
698 void InlineTextBox::paintCompositionBackground(GraphicsContext* context, const IntPoint& boxOrigin, RenderStyle* style, const Font& font, int startPos, int endPos)
700 int offset = m_start;
701 int sPos = max(startPos - offset, 0);
702 int ePos = min(endPos - offset, (int)m_len);
709 Color c = Color(225, 221, 85);
711 updateGraphicsContext(context, c, c, 0, style->colorSpace()); // Don't draw text at all!
713 int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
714 int selHeight = selectionHeight();
715 IntPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
716 context->drawHighlightForText(font, TextRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd,
717 !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()),
718 localOrigin, selHeight, c, style->colorSpace(), sPos, ePos);
724 void InlineTextBox::paintCustomHighlight(int tx, int ty, const AtomicString& type)
726 Frame* frame = renderer()->frame();
729 Page* page = frame->page();
733 RootInlineBox* r = root();
734 FloatRect rootRect(tx + r->x(), ty + selectionTop(), r->logicalWidth(), selectionHeight());
735 FloatRect textRect(tx + x(), rootRect.y(), logicalWidth(), rootRect.height());
737 page->chrome()->client()->paintCustomHighlight(renderer()->node(), type, textRect, rootRect, true, false);
742 void InlineTextBox::paintDecoration(GraphicsContext* context, const IntPoint& boxOrigin, int deco, const ShadowData* shadow)
744 if (m_truncation == cFullTruncation)
747 IntPoint localOrigin = boxOrigin;
749 int width = m_logicalWidth;
750 if (m_truncation != cNoTruncation) {
751 width = toRenderText(renderer())->width(m_start, m_truncation, textPos(), m_firstLine);
752 if (!isLeftToRightDirection())
753 localOrigin.move(m_logicalWidth - width, 0);
756 // Get the text decoration colors.
757 Color underline, overline, linethrough;
758 renderer()->getTextDecorationColors(deco, underline, overline, linethrough, true);
760 // Use a special function for underlines to get the positioning exactly right.
761 bool isPrinting = textRenderer()->document()->printing();
762 context->setStrokeThickness(1.0f); // FIXME: We should improve this rule and not always just assume 1.
764 bool linesAreOpaque = !isPrinting && (!(deco & UNDERLINE) || underline.alpha() == 255) && (!(deco & OVERLINE) || overline.alpha() == 255) && (!(deco & LINE_THROUGH) || linethrough.alpha() == 255);
766 RenderStyle* styleToUse = renderer()->style(m_firstLine);
767 int baseline = styleToUse->font().ascent();
769 bool setClip = false;
771 if (!linesAreOpaque && shadow && shadow->next()) {
773 IntRect clipRect(localOrigin, IntSize(width, baseline + 2));
774 for (const ShadowData* s = shadow; s; s = s->next()) {
775 IntRect shadowRect(localOrigin, IntSize(width, baseline + 2));
776 shadowRect.inflate(s->blur());
777 int shadowX = isHorizontal() ? s->x() : s->y();
778 int shadowY = isHorizontal() ? s->y() : -s->x();
779 shadowRect.move(shadowX, shadowY);
780 clipRect.unite(shadowRect);
781 extraOffset = max(extraOffset, max(0, shadowY) + s->blur());
784 context->clip(clipRect);
785 extraOffset += baseline + 2;
786 localOrigin.move(0, extraOffset);
790 ColorSpace colorSpace = renderer()->style()->colorSpace();
791 bool setShadow = false;
795 if (!shadow->next()) {
796 // The last set of lines paints normally inside the clip.
797 localOrigin.move(0, -extraOffset);
800 int shadowX = isHorizontal() ? shadow->x() : shadow->y();
801 int shadowY = isHorizontal() ? shadow->y() : -shadow->x();
802 context->setShadow(IntSize(shadowX, shadowY - extraOffset), shadow->blur(), shadow->color(), colorSpace);
804 shadow = shadow->next();
807 if (deco & UNDERLINE) {
808 context->setStrokeColor(underline, colorSpace);
809 context->setStrokeStyle(SolidStroke);
810 // Leave one pixel of white between the baseline and the underline.
811 context->drawLineForText(IntPoint(localOrigin.x(), localOrigin.y() + baseline + 1), width, isPrinting);
813 if (deco & OVERLINE) {
814 context->setStrokeColor(overline, colorSpace);
815 context->setStrokeStyle(SolidStroke);
816 context->drawLineForText(localOrigin, width, isPrinting);
818 if (deco & LINE_THROUGH) {
819 context->setStrokeColor(linethrough, colorSpace);
820 context->setStrokeStyle(SolidStroke);
821 context->drawLineForText(IntPoint(localOrigin.x(), localOrigin.y() + 2 * baseline / 3), width, isPrinting);
828 context->clearShadow();
831 static GraphicsContext::TextCheckingLineStyle textCheckingLineStyleForMarkerType(DocumentMarker::MarkerType markerType)
833 switch (markerType) {
834 case DocumentMarker::Spelling:
835 return GraphicsContext::TextCheckingSpellingLineStyle;
836 case DocumentMarker::Grammar:
837 return GraphicsContext::TextCheckingGrammarLineStyle;
838 case DocumentMarker::CorrectionIndicator:
839 return GraphicsContext::TextCheckingReplacementLineStyle;
841 ASSERT_NOT_REACHED();
842 return GraphicsContext::TextCheckingSpellingLineStyle;
846 void InlineTextBox::paintSpellingOrGrammarMarker(GraphicsContext* pt, const IntPoint& boxOrigin, const DocumentMarker& marker, RenderStyle* style, const Font& font, bool grammar)
848 // Never print spelling/grammar markers (5327887)
849 if (textRenderer()->document()->printing())
852 if (m_truncation == cFullTruncation)
855 int start = 0; // start of line to draw, relative to tx
856 int width = m_logicalWidth; // how much line to draw
858 // Determine whether we need to measure text
859 bool markerSpansWholeBox = true;
860 if (m_start <= (int)marker.startOffset)
861 markerSpansWholeBox = false;
862 if ((end() + 1) != marker.endOffset) // end points at the last char, not past it
863 markerSpansWholeBox = false;
864 if (m_truncation != cNoTruncation)
865 markerSpansWholeBox = false;
867 if (!markerSpansWholeBox || grammar) {
868 int startPosition = max<int>(marker.startOffset - m_start, 0);
869 int endPosition = min<int>(marker.endOffset - m_start, m_len);
871 if (m_truncation != cNoTruncation)
872 endPosition = min<int>(endPosition, m_truncation);
874 // Calculate start & width
875 int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
876 int selHeight = selectionHeight();
877 IntPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY);
878 TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered());
880 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, selHeight, startPosition, endPosition));
881 start = markerRect.x() - startPoint.x();
882 width = markerRect.width();
884 // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to
885 // display a toolTip. We don't do this for misspelling markers.
887 markerRect.move(-boxOrigin.x(), -boxOrigin.y());
888 markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
889 renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect);
893 // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to
894 // make sure to fit within those bounds. This means the top pixel(s) of the underline will overlap the
895 // bottom pixel(s) of the glyphs in smaller font sizes. The alternatives are to increase the line spacing (bad!!)
896 // or decrease the underline thickness. The overlap is actually the most useful, and matches what AppKit does.
897 // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so
898 // we pin to two pixels under the baseline.
899 int lineThickness = cMisspellingLineThickness;
900 int baseline = renderer()->style(m_firstLine)->font().ascent();
901 int descent = logicalHeight() - baseline;
903 if (descent <= (2 + lineThickness)) {
904 // Place the underline at the very bottom of the text in small/medium fonts.
905 underlineOffset = logicalHeight() - lineThickness;
907 // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
908 underlineOffset = baseline + 2;
910 pt->drawLineForTextChecking(IntPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, textCheckingLineStyleForMarkerType(marker.type));
913 void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, const IntPoint& boxOrigin, const DocumentMarker& marker, RenderStyle* style, const Font& font)
915 // Use same y positioning and height as for selection, so that when the selection and this highlight are on
916 // the same word there are no pieces sticking out.
917 int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
918 int selHeight = selectionHeight();
920 int sPos = max(marker.startOffset - m_start, (unsigned)0);
921 int ePos = min(marker.endOffset - m_start, (unsigned)m_len);
922 TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered());
924 // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates.
925 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, IntPoint(m_x, selectionTop()), selHeight, sPos, ePos));
926 markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
927 renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect);
929 // Optionally highlight the text
930 if (renderer()->frame()->editor()->markedTextMatchesAreHighlighted()) {
931 Color color = marker.activeMatch ?
932 renderer()->theme()->platformActiveTextSearchHighlightColor() :
933 renderer()->theme()->platformInactiveTextSearchHighlightColor();
935 updateGraphicsContext(pt, color, color, 0, style->colorSpace()); // Don't draw text at all!
936 pt->clip(IntRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selHeight));
937 pt->drawHighlightForText(font, run, IntPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, style->colorSpace(), sPos, ePos);
942 void InlineTextBox::computeRectForReplacementMarker(const DocumentMarker& marker, RenderStyle* style, const Font& font)
944 // Replacement markers are not actually drawn, but their rects need to be computed for hit testing.
945 int y = selectionTop();
946 int h = selectionHeight();
948 int sPos = max(marker.startOffset - m_start, (unsigned)0);
949 int ePos = min(marker.endOffset - m_start, (unsigned)m_len);
950 TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered());
951 IntPoint startPoint = IntPoint(m_x, y);
953 // Compute and store the rect associated with this marker.
954 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, h, sPos, ePos));
955 markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
956 renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect);
959 void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const IntPoint& boxOrigin, RenderStyle* style, const Font& font, bool background)
961 if (!renderer()->node())
964 Vector<DocumentMarker> markers = renderer()->document()->markers()->markersForNode(renderer()->node());
965 Vector<DocumentMarker>::iterator markerIt = markers.begin();
967 // Give any document markers that touch this run a chance to draw before the text has been drawn.
968 // Note end() points at the last char, not one past it like endOffset and ranges do.
969 for ( ; markerIt != markers.end(); markerIt++) {
970 const DocumentMarker& marker = *markerIt;
972 // Paint either the background markers or the foreground markers, but not both
973 switch (marker.type) {
974 case DocumentMarker::Grammar:
975 case DocumentMarker::Spelling:
976 case DocumentMarker::Replacement:
977 case DocumentMarker::CorrectionIndicator:
978 case DocumentMarker::RejectedCorrection:
982 case DocumentMarker::TextMatch:
988 ASSERT_NOT_REACHED();
991 if (marker.endOffset <= start())
992 // marker is completely before this run. This might be a marker that sits before the
993 // first run we draw, or markers that were within runs we skipped due to truncation.
996 if (marker.startOffset > end())
997 // marker is completely after this run, bail. A later run will paint it.
1000 // marker intersects this run. Paint it.
1001 switch (marker.type) {
1002 case DocumentMarker::Spelling:
1003 paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, false);
1005 case DocumentMarker::Grammar:
1006 paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, true);
1008 case DocumentMarker::TextMatch:
1009 paintTextMatchMarker(pt, boxOrigin, marker, style, font);
1011 case DocumentMarker::CorrectionIndicator:
1012 computeRectForReplacementMarker(marker, style, font);
1013 paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, false);
1015 case DocumentMarker::Replacement:
1016 case DocumentMarker::RejectedCorrection:
1019 ASSERT_NOT_REACHED();
1026 void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const IntPoint& boxOrigin, const CompositionUnderline& underline)
1028 if (m_truncation == cFullTruncation)
1031 int start = 0; // start of line to draw, relative to tx
1032 int width = m_logicalWidth; // how much line to draw
1033 bool useWholeWidth = true;
1034 unsigned paintStart = m_start;
1035 unsigned paintEnd = end() + 1; // end points at the last char, not past it
1036 if (paintStart <= underline.startOffset) {
1037 paintStart = underline.startOffset;
1038 useWholeWidth = false;
1039 start = toRenderText(renderer())->width(m_start, paintStart - m_start, textPos(), m_firstLine);
1041 if (paintEnd != underline.endOffset) { // end points at the last char, not past it
1042 paintEnd = min(paintEnd, (unsigned)underline.endOffset);
1043 useWholeWidth = false;
1045 if (m_truncation != cNoTruncation) {
1046 paintEnd = min(paintEnd, (unsigned)m_start + m_truncation);
1047 useWholeWidth = false;
1049 if (!useWholeWidth) {
1050 width = toRenderText(renderer())->width(paintStart, paintEnd - paintStart, textPos() + start, m_firstLine);
1053 // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
1054 // All other marked text underlines are 1px thick.
1055 // If there's not enough space the underline will touch or overlap characters.
1056 int lineThickness = 1;
1057 int baseline = renderer()->style(m_firstLine)->font().ascent();
1058 if (underline.thick && logicalHeight() - baseline >= 2)
1061 // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
1062 // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
1066 ctx->setStrokeColor(underline.color, renderer()->style()->colorSpace());
1067 ctx->setStrokeThickness(lineThickness);
1068 ctx->drawLineForText(IntPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, textRenderer()->document()->printing());
1071 int InlineTextBox::caretMinOffset() const
1076 int InlineTextBox::caretMaxOffset() const
1078 return m_start + m_len;
1081 unsigned InlineTextBox::caretMaxRenderedOffset() const
1083 return m_start + m_len;
1086 int InlineTextBox::textPos() const
1088 // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset
1089 // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width.
1090 if (logicalLeft() == 0)
1092 return logicalLeft() - root()->logicalLeft();
1095 int InlineTextBox::offsetForPosition(int lineOffset, bool includePartialGlyphs) const
1100 RenderText* text = toRenderText(renderer());
1101 RenderStyle* style = text->style(m_firstLine);
1102 const Font* f = &style->font();
1103 return f->offsetForPosition(TextRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()),
1104 lineOffset - logicalLeft(), includePartialGlyphs);
1107 int InlineTextBox::positionForOffset(int offset) const
1109 ASSERT(offset >= m_start);
1110 ASSERT(offset <= m_start + m_len);
1113 return logicalLeft();
1115 RenderText* text = toRenderText(renderer());
1116 const Font& f = text->style(m_firstLine)->font();
1117 int from = !isLeftToRightDirection() ? offset - m_start : 0;
1118 int to = !isLeftToRightDirection() ? m_len : offset - m_start;
1119 // FIXME: Do we need to add rightBearing here?
1120 return enclosingIntRect(f.selectionRectForText(TextRun(text->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_toAdd, !isLeftToRightDirection(), m_dirOverride),
1121 IntPoint(logicalLeft(), 0), 0, from, to)).right();
1124 bool InlineTextBox::containsCaretOffset(int offset) const
1126 // Offsets before the box are never "in".
1127 if (offset < m_start)
1130 int pastEnd = m_start + m_len;
1132 // Offsets inside the box (not at either edge) are always "in".
1133 if (offset < pastEnd)
1136 // Offsets outside the box are always "out".
1137 if (offset > pastEnd)
1140 // Offsets at the end are "out" for line breaks (they are on the next line).
1144 // Offsets at the end are "in" for normal boxes (but the caller has to check affinity).
1148 } // namespace WebCore