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.
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 "core/rendering/InlineTextBox.h"
26 #include "core/dom/Document.h"
27 #include "core/dom/DocumentMarkerController.h"
28 #include "core/dom/RenderedDocumentMarker.h"
29 #include "core/dom/Text.h"
30 #include "core/editing/Editor.h"
31 #include "core/editing/InputMethodController.h"
32 #include "core/frame/Frame.h"
33 #include "core/page/Page.h"
34 #include "core/page/Settings.h"
35 #include "core/platform/graphics/FontCache.h"
36 #include "core/platform/graphics/GraphicsContextStateSaver.h"
37 #include "core/platform/graphics/WidthIterator.h"
38 #include "core/rendering/AbstractInlineTextBox.h"
39 #include "core/rendering/EllipsisBox.h"
40 #include "core/rendering/HitTestResult.h"
41 #include "core/rendering/PaintInfo.h"
42 #include "core/rendering/RenderBR.h"
43 #include "core/rendering/RenderBlock.h"
44 #include "core/rendering/RenderCombineText.h"
45 #include "core/rendering/RenderRubyRun.h"
46 #include "core/rendering/RenderRubyText.h"
47 #include "core/rendering/RenderTheme.h"
48 #include "core/rendering/style/ShadowList.h"
49 #include "core/rendering/svg/SVGTextRunRenderingContext.h"
50 #include "platform/graphics/DrawLooper.h"
51 #include "wtf/Vector.h"
52 #include "wtf/text/CString.h"
53 #include "wtf/text/StringBuilder.h"
59 struct SameSizeAsInlineTextBox : public InlineBox {
60 unsigned variables[1];
61 unsigned short variables2[2];
65 COMPILE_ASSERT(sizeof(InlineTextBox) == sizeof(SameSizeAsInlineTextBox), InlineTextBox_should_stay_small);
67 typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap;
68 static InlineTextBoxOverflowMap* gTextBoxesWithOverflow;
70 static const int misspellingLineThickness = 3;
72 void InlineTextBox::destroy()
74 AbstractInlineTextBox::willDestroy(this);
76 if (!knownToHaveNoOverflow() && gTextBoxesWithOverflow)
77 gTextBoxesWithOverflow->remove(this);
81 void InlineTextBox::markDirty(bool dirty)
87 InlineBox::markDirty(dirty);
90 LayoutRect InlineTextBox::logicalOverflowRect() const
92 if (knownToHaveNoOverflow() || !gTextBoxesWithOverflow)
93 return enclosingIntRect(logicalFrameRect());
94 return gTextBoxesWithOverflow->get(this);
97 void InlineTextBox::setLogicalOverflowRect(const LayoutRect& rect)
99 ASSERT(!knownToHaveNoOverflow());
100 if (!gTextBoxesWithOverflow)
101 gTextBoxesWithOverflow = new InlineTextBoxOverflowMap;
102 gTextBoxesWithOverflow->add(this, rect);
105 int InlineTextBox::baselinePosition(FontBaseline baselineType) const
107 if (!isText() || !parent())
109 if (parent()->renderer() == renderer()->parent())
110 return parent()->baselinePosition(baselineType);
111 return toRenderBoxModelObject(renderer()->parent())->baselinePosition(baselineType, isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
114 LayoutUnit InlineTextBox::lineHeight() const
116 if (!isText() || !renderer()->parent())
118 if (m_renderer->isBR())
119 return toRenderBR(m_renderer)->lineHeight(isFirstLineStyle());
120 if (parent()->renderer() == renderer()->parent())
121 return parent()->lineHeight();
122 return toRenderBoxModelObject(renderer()->parent())->lineHeight(isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
125 LayoutUnit InlineTextBox::selectionTop()
127 return root()->selectionTop();
130 LayoutUnit InlineTextBox::selectionBottom()
132 return root()->selectionBottom();
135 LayoutUnit InlineTextBox::selectionHeight()
137 return root()->selectionHeight();
140 bool InlineTextBox::isSelected(int startPos, int endPos) const
142 int sPos = max(startPos - m_start, 0);
143 // The position after a hard line break is considered to be past its end.
144 // See the corresponding code in InlineTextBox::selectionState.
145 int ePos = min(endPos - m_start, int(m_len) + (isLineBreak() ? 0 : 1));
146 return (sPos < ePos);
149 RenderObject::SelectionState InlineTextBox::selectionState()
151 RenderObject::SelectionState state = renderer()->selectionState();
152 if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
153 int startPos, endPos;
154 renderer()->selectionStartEnd(startPos, endPos);
155 // The position after a hard line break is considered to be past its end.
156 // See the corresponding code in InlineTextBox::isSelected.
157 int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0);
159 // FIXME: Remove -webkit-line-break: LineBreakAfterWhiteSpace.
160 int endOfLineAdjustmentForCSSLineBreak = renderer()->style()->lineBreak() == LineBreakAfterWhiteSpace ? -1 : 0;
161 bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos <= m_start + m_len + endOfLineAdjustmentForCSSLineBreak);
162 bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
164 state = RenderObject::SelectionBoth;
166 state = RenderObject::SelectionStart;
168 state = RenderObject::SelectionEnd;
169 else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
170 (state == RenderObject::SelectionStart || endPos > lastSelectable))
171 state = RenderObject::SelectionInside;
172 else if (state == RenderObject::SelectionBoth)
173 state = RenderObject::SelectionNone;
176 // If there are ellipsis following, make sure their selection is updated.
177 if (m_truncation != cNoTruncation && root()->ellipsisBox()) {
178 EllipsisBox* ellipsis = root()->ellipsisBox();
179 if (state != RenderObject::SelectionNone) {
181 selectionStartEnd(start, end);
182 // The ellipsis should be considered to be selected if the end of
183 // the selection is past the beginning of the truncation and the
184 // beginning of the selection is before or at the beginning of the
186 ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ?
187 RenderObject::SelectionInside : RenderObject::SelectionNone);
189 ellipsis->setSelectionState(RenderObject::SelectionNone);
195 LayoutRect InlineTextBox::localSelectionRect(int startPos, int endPos)
197 int sPos = max(startPos - m_start, 0);
198 int ePos = min(endPos - m_start, (int)m_len);
203 FontCachePurgePreventer fontCachePurgePreventer;
205 RenderText* textObj = textRenderer();
206 LayoutUnit selTop = selectionTop();
207 LayoutUnit selHeight = selectionHeight();
208 RenderStyle* styleToUse = textObj->style(isFirstLineStyle());
209 const Font& font = styleToUse->font();
211 StringBuilder charactersWithHyphen;
212 bool respectHyphen = ePos == m_len && hasHyphen();
213 TextRun textRun = constructTextRun(styleToUse, font, respectHyphen ? &charactersWithHyphen : 0);
215 endPos = textRun.length();
217 FloatPoint startingPoint = FloatPoint(logicalLeft(), selTop);
219 if (sPos || ePos != static_cast<int>(m_len))
220 r = enclosingIntRect(font.selectionRectForText(textRun, startingPoint, selHeight, sPos, ePos));
221 else // Avoid computing the font width when the entire line box is selected as an optimization.
222 r = enclosingIntRect(FloatRect(startingPoint, FloatSize(m_logicalWidth, selHeight)));
224 LayoutUnit logicalWidth = r.width();
225 if (r.x() > logicalRight())
227 else if (r.maxX() > logicalRight())
228 logicalWidth = logicalRight() - r.x();
230 LayoutPoint topPoint = isHorizontal() ? LayoutPoint(r.x(), selTop) : LayoutPoint(selTop, r.x());
231 LayoutUnit width = isHorizontal() ? logicalWidth : selHeight;
232 LayoutUnit height = isHorizontal() ? selHeight : logicalWidth;
234 return LayoutRect(topPoint, LayoutSize(width, height));
237 void InlineTextBox::deleteLine()
239 toRenderText(renderer())->removeTextBox(this);
243 void InlineTextBox::extractLine()
248 toRenderText(renderer())->extractTextBox(this);
251 void InlineTextBox::attachLine()
256 toRenderText(renderer())->attachTextBox(this);
259 float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, float &truncatedWidth, bool& foundBox)
262 m_truncation = cFullTruncation;
266 // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
267 float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
269 // Criteria for full truncation:
270 // LTR: the left edge of the ellipsis is to the left of our text run.
271 // RTL: the right edge of the ellipsis is to the right of our text run.
272 bool ltrFullTruncation = flowIsLTR && ellipsisX <= left();
273 bool rtlFullTruncation = !flowIsLTR && ellipsisX >= left() + logicalWidth();
274 if (ltrFullTruncation || rtlFullTruncation) {
275 // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
276 m_truncation = cFullTruncation;
281 bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < right());
282 bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > left());
283 if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
286 // The inline box may have different directionality than it's parent. Since truncation
287 // behavior depends both on both the parent and the inline block's directionality, we
288 // must keep track of these separately.
289 bool ltr = isLeftToRightDirection();
290 if (ltr != flowIsLTR) {
291 // Width in pixels of the visible portion of the box, excluding the ellipsis.
292 int visibleBoxWidth = visibleRightEdge - visibleLeftEdge - ellipsisWidth;
293 ellipsisX = ltr ? left() + visibleBoxWidth : right() - visibleBoxWidth;
296 int offset = offsetForPosition(ellipsisX, false);
298 // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start
299 // and the ellipsis edge.
300 m_truncation = cFullTruncation;
301 truncatedWidth += ellipsisWidth;
302 return min(ellipsisX, x());
305 // Set the truncation index on the text run.
306 m_truncation = offset;
308 // If we got here that means that we were only partially truncated and we need to return the pixel offset at which
309 // to place the ellipsis.
310 float widthOfVisibleText = toRenderText(renderer())->width(m_start, offset, textPos(), isFirstLineStyle());
312 // The ellipsis needs to be placed just after the last visible character.
313 // Where "after" is defined by the flow directionality, not the inline
314 // box directionality.
315 // e.g. In the case of an LTR inline box truncated in an RTL flow then we can
316 // have a situation such as |Hello| -> |...He|
317 truncatedWidth += widthOfVisibleText + ellipsisWidth;
319 return left() + widthOfVisibleText;
321 return right() - widthOfVisibleText - ellipsisWidth;
323 truncatedWidth += logicalWidth();
327 Color correctedTextColor(Color textColor, Color backgroundColor)
329 // Adjust the text color if it is too close to the background color,
330 // by darkening or lightening it to move it further away.
332 int d = differenceSquared(textColor, backgroundColor);
333 // semi-arbitrarily chose 65025 (255^2) value here after a few tests;
338 int distanceFromWhite = differenceSquared(textColor, Color::white);
339 int distanceFromBlack = differenceSquared(textColor, Color::black);
341 if (distanceFromWhite < distanceFromBlack) {
342 return textColor.dark();
345 return textColor.light();
348 void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness)
350 TextDrawingModeFlags mode = context->textDrawingMode();
351 if (strokeThickness > 0) {
352 TextDrawingModeFlags newMode = mode | TextModeStroke;
353 if (mode != newMode) {
354 context->setTextDrawingMode(newMode);
359 if (mode & TextModeFill && fillColor != context->fillColor())
360 context->setFillColor(fillColor);
362 if (mode & TextModeStroke) {
363 if (strokeColor != context->strokeColor())
364 context->setStrokeColor(strokeColor);
365 if (strokeThickness != context->strokeThickness())
366 context->setStrokeThickness(strokeThickness);
370 bool InlineTextBox::isLineBreak() const
372 return renderer()->isBR() || (renderer()->style()->preserveNewline() && len() == 1 && (*textRenderer()->text().impl())[start()] == '\n');
375 bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/)
380 FloatPoint boxOrigin = locationIncludingFlipping();
381 boxOrigin.moveBy(accumulatedOffset);
382 FloatRect rect(boxOrigin, size());
383 if (m_truncation != cFullTruncation && visibleToHitTestRequest(request) && locationInContainer.intersects(rect)) {
384 renderer()->updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset)));
385 if (!result.addNodeToRectBasedTestResult(renderer()->node(), request, locationInContainer, rect))
391 static void paintTextWithShadows(GraphicsContext* context,
392 const RenderObject* renderer, const Font& font, const TextRun& textRun,
393 const AtomicString& emphasisMark, int emphasisMarkOffset,
394 int startOffset, int endOffset, int truncationPoint,
395 const FloatPoint& textOrigin, const FloatRect& boxRect,
396 const ShadowList* shadowList, bool stroked, bool horizontal)
398 // Text shadows are disabled when printing. http://crbug.com/258321
399 bool hasShadow = shadowList && !context->printing();
402 DrawLooper drawLooper;
403 for (size_t i = shadowList->shadows().size(); i--; ) {
404 const ShadowData& shadow = shadowList->shadows()[i];
405 int shadowX = horizontal ? shadow.x() : shadow.y();
406 int shadowY = horizontal ? shadow.y() : -shadow.x();
407 FloatSize offset(shadowX, shadowY);
408 drawLooper.addShadow(offset, shadow.blur(), renderer->resolveColor(shadow.color()),
409 DrawLooper::ShadowRespectsTransforms, DrawLooper::ShadowIgnoresAlpha);
411 drawLooper.addUnmodifiedContent();
412 context->setDrawLooper(drawLooper);
415 TextRunPaintInfo textRunPaintInfo(textRun);
416 textRunPaintInfo.bounds = boxRect;
417 if (startOffset <= endOffset) {
418 textRunPaintInfo.from = startOffset;
419 textRunPaintInfo.to = endOffset;
420 if (emphasisMark.isEmpty())
421 context->drawText(font, textRunPaintInfo, textOrigin);
423 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset));
426 textRunPaintInfo.from = 0;
427 textRunPaintInfo.to = endOffset;
428 if (emphasisMark.isEmpty())
429 context->drawText(font, textRunPaintInfo, textOrigin);
431 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset));
433 if (startOffset < truncationPoint) {
434 textRunPaintInfo.from = startOffset;
435 textRunPaintInfo.to = truncationPoint;
436 if (emphasisMark.isEmpty())
437 context->drawText(font, textRunPaintInfo, textOrigin);
439 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset));
444 context->clearDrawLooper();
447 bool InlineTextBox::getEmphasisMarkPosition(RenderStyle* style, TextEmphasisPosition& emphasisPosition) const
449 // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
450 if (style->textEmphasisMark() == TextEmphasisMarkNone)
453 emphasisPosition = style->textEmphasisPosition();
454 if (emphasisPosition == TextEmphasisPositionUnder)
455 return true; // Ruby text is always over, so it cannot suppress emphasis marks under.
457 RenderBlock* containingBlock = renderer()->containingBlock();
458 if (!containingBlock->isRubyBase())
459 return true; // This text is not inside a ruby base, so it does not have ruby text over it.
461 if (!containingBlock->parent()->isRubyRun())
462 return true; // Cannot get the ruby text.
464 RenderRubyText* rubyText = toRenderRubyRun(containingBlock->parent())->rubyText();
466 // The emphasis marks over are suppressed only if there is a ruby text box and it not empty.
467 return !rubyText || !rubyText->firstLineBox();
470 enum RotationDirection { Counterclockwise, Clockwise };
472 static inline AffineTransform rotation(const FloatRect& boxRect, RotationDirection clockwise)
474 return clockwise ? AffineTransform(0, 1, -1, 0, boxRect.x() + boxRect.maxY(), boxRect.maxY() - boxRect.x())
475 : AffineTransform(0, -1, 1, 0, boxRect.x() - boxRect.maxY(), boxRect.x() + boxRect.maxY());
478 void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/)
480 if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer()->style()->visibility() != VISIBLE ||
481 m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len)
484 ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
486 LayoutUnit logicalLeftSide = logicalLeftVisualOverflow();
487 LayoutUnit logicalRightSide = logicalRightVisualOverflow();
488 LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y());
489 LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide;
491 LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
492 LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
494 LayoutPoint adjustedPaintOffset = roundedIntPoint(paintOffset);
496 if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
499 bool isPrinting = textRenderer()->document().printing();
501 // Determine whether or not we're selected.
502 bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
503 if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
504 // When only painting the selection, don't bother to paint if there is none.
507 if (m_truncation != cNoTruncation) {
508 if (renderer()->containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) {
509 // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
510 // at which we start drawing text.
511 // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
512 // |Hello|CBA| -> |...He|CBA|
513 // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
514 // farther to the right.
515 // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
516 // truncated string i.e. |Hello|CBA| -> |...lo|CBA|
517 LayoutUnit widthOfVisibleText = toRenderText(renderer())->width(m_start, m_truncation, textPos(), isFirstLineStyle());
518 LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
519 // FIXME: The hit testing logic also needs to take this translation into account.
520 LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0);
521 adjustedPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
525 GraphicsContext* context = paintInfo.context;
527 RenderObject* rendererToUse = renderer();
528 RenderStyle* styleToUse = rendererToUse->style(isFirstLineStyle());
530 adjustedPaintOffset.move(0, styleToUse->isHorizontalWritingMode() ? 0 : -logicalHeight());
532 FloatPoint boxOrigin = locationIncludingFlipping();
533 boxOrigin.move(adjustedPaintOffset.x(), adjustedPaintOffset.y());
534 FloatRect boxRect(boxOrigin, LayoutSize(logicalWidth(), logicalHeight()));
536 RenderCombineText* combinedText = styleToUse->hasTextCombine() && textRenderer()->isCombineText() && toRenderCombineText(textRenderer())->isCombined() ? toRenderCombineText(textRenderer()) : 0;
538 bool shouldRotate = !isHorizontal() && !combinedText;
540 context->concatCTM(rotation(boxRect, Clockwise));
542 // Determine whether or not we have composition underlines to draw.
543 bool containsComposition = renderer()->node() && renderer()->frame()->inputMethodController().compositionNode() == renderer()->node();
544 bool useCustomUnderlines = containsComposition && renderer()->frame()->inputMethodController().compositionUsesCustomUnderlines();
546 // Determine the text colors and selection colors.
548 Color textStrokeColor;
549 Color emphasisMarkColor;
550 float textStrokeWidth = styleToUse->textStrokeWidth();
552 // Text shadows are disabled when printing. http://crbug.com/258321
553 const ShadowList* textShadow = (context->printing() || paintInfo.forceBlackText()) ? 0 : styleToUse->textShadow();
555 if (paintInfo.forceBlackText()) {
556 textFillColor = Color::black;
557 textStrokeColor = Color::black;
558 emphasisMarkColor = Color::black;
560 textFillColor = rendererToUse->resolveColor(styleToUse, CSSPropertyWebkitTextFillColor);
562 bool forceBackgroundToWhite = false;
564 if (styleToUse->printColorAdjust() == PrintColorAdjustEconomy)
565 forceBackgroundToWhite = true;
566 if (textRenderer()->document().settings() && textRenderer()->document().settings()->shouldPrintBackgrounds())
567 forceBackgroundToWhite = false;
570 // Make the text fill color legible against a white background
571 if (forceBackgroundToWhite)
572 textFillColor = correctedTextColor(textFillColor, Color::white);
574 textStrokeColor = rendererToUse->resolveColor(styleToUse, CSSPropertyWebkitTextStrokeColor);
576 // Make the text stroke color legible against a white background
577 if (forceBackgroundToWhite)
578 textStrokeColor = correctedTextColor(textStrokeColor, Color::white);
580 emphasisMarkColor = rendererToUse->resolveColor(styleToUse, CSSPropertyWebkitTextEmphasisColor);
582 // Make the text stroke color legible against a white background
583 if (forceBackgroundToWhite)
584 emphasisMarkColor = correctedTextColor(emphasisMarkColor, Color::white);
587 bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection);
588 bool paintSelectedTextSeparately = false;
590 Color selectionFillColor = textFillColor;
591 Color selectionStrokeColor = textStrokeColor;
592 Color selectionEmphasisMarkColor = emphasisMarkColor;
593 float selectionStrokeWidth = textStrokeWidth;
594 const ShadowList* selectionShadow = textShadow;
596 // Check foreground color first.
597 Color foreground = paintInfo.forceBlackText() ? Color::black : renderer()->selectionForegroundColor();
598 if (foreground.isValid() && foreground != selectionFillColor && foreground != Color::transparent) {
599 if (!paintSelectedTextOnly)
600 paintSelectedTextSeparately = true;
601 selectionFillColor = foreground;
604 Color emphasisMarkForeground = paintInfo.forceBlackText() ? Color::black : renderer()->selectionEmphasisMarkColor();
605 if (emphasisMarkForeground.isValid() && emphasisMarkForeground != selectionEmphasisMarkColor) {
606 if (!paintSelectedTextOnly)
607 paintSelectedTextSeparately = true;
608 selectionEmphasisMarkColor = emphasisMarkForeground;
611 if (RenderStyle* pseudoStyle = renderer()->getCachedPseudoStyle(SELECTION)) {
612 // Text shadows are disabled when printing. http://crbug.com/258321
613 const ShadowList* shadow = (context->printing() || paintInfo.forceBlackText()) ? 0 : pseudoStyle->textShadow();
614 if (shadow != selectionShadow) {
615 if (!paintSelectedTextOnly)
616 paintSelectedTextSeparately = true;
617 selectionShadow = shadow;
620 float strokeWidth = pseudoStyle->textStrokeWidth();
621 if (strokeWidth != selectionStrokeWidth) {
622 if (!paintSelectedTextOnly)
623 paintSelectedTextSeparately = true;
624 selectionStrokeWidth = strokeWidth;
627 Color stroke = paintInfo.forceBlackText() ? Color::black : rendererToUse->resolveColor(pseudoStyle, CSSPropertyWebkitTextStrokeColor);
628 if (stroke != selectionStrokeColor) {
629 if (!paintSelectedTextOnly)
630 paintSelectedTextSeparately = true;
631 selectionStrokeColor = stroke;
637 const Font& font = styleToUse->font();
639 FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());
642 combinedText->adjustTextOrigin(textOrigin, boxRect);
644 // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
645 // and composition underlines.
646 if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
648 if (containsComposition && !useCustomUnderlines) {
649 paintCompositionBackground(context, boxOrigin, styleToUse, font,
650 renderer()->frame()->inputMethodController().compositionStart(),
651 renderer()->frame()->inputMethodController().compositionEnd());
654 paintDocumentMarkers(context, boxOrigin, styleToUse, font, true);
656 if (haveSelection && !useCustomUnderlines)
657 paintSelection(context, boxOrigin, styleToUse, font, selectionFillColor);
660 // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
665 string = textRenderer()->text().createView();
666 if (static_cast<unsigned>(length) != string.length() || m_start)
667 string.narrow(m_start, length);
668 maximumLength = textRenderer()->textLength() - m_start;
670 combinedText->getStringToRender(m_start, string, length);
671 maximumLength = length;
674 StringBuilder charactersWithHyphen;
675 TextRun textRun = constructTextRun(styleToUse, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0);
677 length = textRun.length();
681 if (paintSelectedTextOnly || paintSelectedTextSeparately)
682 selectionStartEnd(sPos, ePos);
684 if (m_truncation != cNoTruncation) {
685 sPos = min<int>(sPos, m_truncation);
686 ePos = min<int>(ePos, m_truncation);
687 length = m_truncation;
690 int emphasisMarkOffset = 0;
691 TextEmphasisPosition emphasisMarkPosition;
692 bool hasTextEmphasis = getEmphasisMarkPosition(styleToUse, emphasisMarkPosition);
693 const AtomicString& emphasisMark = hasTextEmphasis ? styleToUse->textEmphasisMarkString() : nullAtom;
694 if (!emphasisMark.isEmpty())
695 emphasisMarkOffset = emphasisMarkPosition == TextEmphasisPositionOver ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
697 if (!paintSelectedTextOnly) {
698 // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side
699 // effect, so only when we know we're stroking, do a save/restore.
700 GraphicsContextStateSaver stateSaver(*context, textStrokeWidth > 0);
702 updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth);
703 if (!paintSelectedTextSeparately || ePos <= sPos) {
704 // FIXME: Truncate right-to-left text correctly.
705 paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
707 paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
710 if (!emphasisMark.isEmpty()) {
711 updateGraphicsContext(context, emphasisMarkColor, textStrokeColor, textStrokeWidth);
713 DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
714 TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
715 FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
717 context->concatCTM(rotation(boxRect, Clockwise));
719 if (!paintSelectedTextSeparately || ePos <= sPos) {
720 // FIXME: Truncate right-to-left text correctly.
721 paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, 0, length, length, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
723 paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, ePos, sPos, length, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
727 context->concatCTM(rotation(boxRect, Counterclockwise));
731 if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) {
732 // paint only the text that is selected
733 GraphicsContextStateSaver stateSaver(*context, selectionStrokeWidth > 0);
735 updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth);
736 paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
737 if (!emphasisMark.isEmpty()) {
738 updateGraphicsContext(context, selectionEmphasisMarkColor, textStrokeColor, textStrokeWidth);
740 DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
741 TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
742 FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
744 context->concatCTM(rotation(boxRect, Clockwise));
746 paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, sPos, ePos, length, emphasisMarkTextOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
749 context->concatCTM(rotation(boxRect, Counterclockwise));
754 TextDecoration textDecorations = styleToUse->textDecorationsInEffect();
755 if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) {
756 updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth);
758 context->concatCTM(rotation(boxRect, Clockwise));
759 paintDecoration(context, boxOrigin, textDecorations, styleToUse->textDecorationStyle(), textShadow);
761 context->concatCTM(rotation(boxRect, Counterclockwise));
764 if (paintInfo.phase == PaintPhaseForeground) {
765 paintDocumentMarkers(context, boxOrigin, styleToUse, font, false);
767 if (useCustomUnderlines) {
768 const Vector<CompositionUnderline>& underlines = renderer()->frame()->inputMethodController().customCompositionUnderlines();
769 size_t numUnderlines = underlines.size();
771 for (size_t index = 0; index < numUnderlines; ++index) {
772 const CompositionUnderline& underline = underlines[index];
774 if (underline.endOffset <= start())
775 // underline is completely before this run. This might be an underline that sits
776 // before the first run we draw, or underlines that were within runs we skipped
777 // due to truncation.
780 if (underline.startOffset <= end()) {
781 // underline intersects this run. Paint it.
782 paintCompositionUnderline(context, boxOrigin, underline);
783 if (underline.endOffset > end() + 1)
784 // underline also runs into the next run. Bail now, no more marker advancement.
787 // underline is completely after this run, bail. A later run will paint it.
794 context->concatCTM(rotation(boxRect, Counterclockwise));
797 void InlineTextBox::selectionStartEnd(int& sPos, int& ePos)
799 int startPos, endPos;
800 if (renderer()->selectionState() == RenderObject::SelectionInside) {
802 endPos = textRenderer()->textLength();
804 textRenderer()->selectionStartEnd(startPos, endPos);
805 if (renderer()->selectionState() == RenderObject::SelectionStart)
806 endPos = textRenderer()->textLength();
807 else if (renderer()->selectionState() == RenderObject::SelectionEnd)
811 sPos = max(startPos - m_start, 0);
812 ePos = min(endPos - m_start, (int)m_len);
815 void alignSelectionRectToDevicePixels(FloatRect& rect)
817 float maxX = floorf(rect.maxX());
818 rect.setX(floorf(rect.x()));
819 rect.setWidth(roundf(maxX - rect.x()));
822 void InlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, Color textColor)
824 if (context->paintingDisabled())
827 // See if we have a selection to paint at all.
829 selectionStartEnd(sPos, ePos);
833 Color c = renderer()->selectionBackgroundColor();
834 if (!c.isValid() || !c.alpha())
837 // If the text color ends up being the same as the selection background, invert the selection
840 c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
842 GraphicsContextStateSaver stateSaver(*context);
843 updateGraphicsContext(context, c, c, 0); // Don't draw text at all!
845 // If the text is truncated, let the thing being painted in the truncation
846 // draw its own highlight.
847 int length = m_truncation != cNoTruncation ? m_truncation : m_len;
848 StringView string = textRenderer()->text().createView();
850 if (string.length() != static_cast<unsigned>(length) || m_start)
851 string.narrow(m_start, length);
853 StringBuilder charactersWithHyphen;
854 bool respectHyphen = ePos == length && hasHyphen();
855 TextRun textRun = constructTextRun(style, font, string, textRenderer()->textLength() - m_start, respectHyphen ? &charactersWithHyphen : 0);
857 ePos = textRun.length();
859 LayoutUnit selectionBottom = root()->selectionBottom();
860 LayoutUnit selectionTop = root()->selectionTopAdjustedForPrecedingBlock();
862 int deltaY = roundToInt(renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop);
863 int selHeight = max(0, roundToInt(selectionBottom - selectionTop));
865 FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
866 FloatRect clipRect(localOrigin, FloatSize(m_logicalWidth, selHeight));
867 alignSelectionRectToDevicePixels(clipRect);
869 context->clip(clipRect);
871 context->drawHighlightForText(font, textRun, localOrigin, selHeight, c, sPos, ePos);
874 void InlineTextBox::paintCompositionBackground(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, int startPos, int endPos)
876 int offset = m_start;
877 int sPos = max(startPos - offset, 0);
878 int ePos = min(endPos - offset, (int)m_len);
883 GraphicsContextStateSaver stateSaver(*context);
885 Color c = Color(225, 221, 85);
887 updateGraphicsContext(context, c, c, 0); // Don't draw text at all!
889 int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
890 int selHeight = selectionHeight();
891 FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
892 context->drawHighlightForText(font, constructTextRun(style, font), localOrigin, selHeight, c, sPos, ePos);
895 static StrokeStyle textDecorationStyleToStrokeStyle(TextDecorationStyle decorationStyle)
897 StrokeStyle strokeStyle = SolidStroke;
898 switch (decorationStyle) {
899 case TextDecorationStyleSolid:
900 strokeStyle = SolidStroke;
902 case TextDecorationStyleDouble:
903 strokeStyle = DoubleStroke;
905 case TextDecorationStyleDotted:
906 strokeStyle = DottedStroke;
908 case TextDecorationStyleDashed:
909 strokeStyle = DashedStroke;
911 case TextDecorationStyleWavy:
912 strokeStyle = WavyStroke;
919 static int computeUnderlineOffset(const TextUnderlinePosition underlinePosition, const FontMetrics& fontMetrics, const InlineTextBox* inlineTextBox, const float textDecorationThickness)
921 // Compute the gap between the font and the underline. Use at least one
922 // pixel gap, if underline is thick then use a bigger gap.
923 const int gap = std::max<int>(1, ceilf(textDecorationThickness / 2.f));
925 // According to the specification TextUnderlinePositionAuto should default to 'alphabetic' for horizontal text
926 // and to 'under Left' for vertical text (e.g. japanese). We support only horizontal text for now.
927 switch (underlinePosition) {
928 case TextUnderlinePositionAlphabetic:
929 case TextUnderlinePositionAuto:
930 return fontMetrics.ascent() + gap; // Position underline near the alphabetic baseline.
931 case TextUnderlinePositionUnder: {
932 // Position underline relative to the under edge of the lowest element's content box.
933 const float offset = inlineTextBox->root()->maxLogicalTop() - inlineTextBox->logicalTop();
935 return inlineTextBox->logicalHeight() + gap + offset;
936 return inlineTextBox->logicalHeight() + gap;
940 ASSERT_NOT_REACHED();
941 return fontMetrics.ascent() + gap;
944 static void adjustStepToDecorationLength(float& step, float& controlPointDistance, float length)
951 unsigned stepCount = static_cast<unsigned>(length / step);
953 // Each Bezier curve starts at the same pixel that the previous one
954 // ended. We need to subtract (stepCount - 1) pixels when calculating the
955 // length covered to account for that.
956 float uncoveredLength = length - (stepCount * step - (stepCount - 1));
957 float adjustment = uncoveredLength / stepCount;
959 controlPointDistance += adjustment;
963 * Draw one cubic Bezier curve and repeat the same pattern long the the decoration's axis.
964 * The start point (p1), controlPoint1, controlPoint2 and end point (p2) of the Bezier curve
965 * form a diamond shape:
977 * (x1, y1) p1 + . + p2 (x2, y2) - <--- Decoration's axis
980 * . . | controlPointDistance
989 static void strokeWavyTextDecoration(GraphicsContext* context, FloatPoint& p1, FloatPoint& p2, float strokeThickness)
991 context->adjustLineToPixelBoundaries(p1, p2, strokeThickness, context->strokeStyle());
996 // Distance between decoration's axis and Bezier curve's control points.
997 // The height of the curve is based on this distance. Use a minimum of 6 pixels distance since
998 // the actual curve passes approximately at half of that distance, that is 3 pixels.
999 // The minimum height of the curve is also approximately 3 pixels. Increases the curve's height
1000 // as strockThickness increases to make the curve looks better.
1001 float controlPointDistance = 3 * max<float>(2, strokeThickness);
1003 // Increment used to form the diamond shape between start point (p1), control
1004 // points and end point (p2) along the axis of the decoration. Makes the
1005 // curve wider as strockThickness increases to make the curve looks better.
1006 float step = 2 * max<float>(2, strokeThickness);
1008 bool isVerticalLine = (p1.x() == p2.x());
1010 if (isVerticalLine) {
1011 ASSERT(p1.x() == p2.x());
1013 float xAxis = p1.x();
1017 if (p1.y() < p2.y()) {
1025 adjustStepToDecorationLength(step, controlPointDistance, y2 - y1);
1026 FloatPoint controlPoint1(xAxis + controlPointDistance, 0);
1027 FloatPoint controlPoint2(xAxis - controlPointDistance, 0);
1029 for (float y = y1; y + 2 * step <= y2;) {
1030 controlPoint1.setY(y + step);
1031 controlPoint2.setY(y + step);
1033 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y));
1036 ASSERT(p1.y() == p2.y());
1038 float yAxis = p1.y();
1042 if (p1.x() < p2.x()) {
1050 adjustStepToDecorationLength(step, controlPointDistance, x2 - x1);
1051 FloatPoint controlPoint1(0, yAxis + controlPointDistance);
1052 FloatPoint controlPoint2(0, yAxis - controlPointDistance);
1054 for (float x = x1; x + 2 * step <= x2;) {
1055 controlPoint1.setX(x + step);
1056 controlPoint2.setX(x + step);
1058 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis));
1062 context->setShouldAntialias(true);
1063 context->strokePath(path);
1066 void InlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& boxOrigin, TextDecoration deco, TextDecorationStyle decorationStyle, const ShadowList* shadowList)
1068 GraphicsContextStateSaver stateSaver(*context);
1070 if (m_truncation == cFullTruncation)
1073 FloatPoint localOrigin = boxOrigin;
1075 float width = m_logicalWidth;
1076 if (m_truncation != cNoTruncation) {
1077 width = toRenderText(renderer())->width(m_start, m_truncation, textPos(), isFirstLineStyle());
1078 if (!isLeftToRightDirection())
1079 localOrigin.move(m_logicalWidth - width, 0);
1082 // Get the text decoration colors.
1083 Color underline, overline, linethrough;
1084 renderer()->getTextDecorationColors(deco, underline, overline, linethrough, true);
1085 if (isFirstLineStyle())
1086 renderer()->getTextDecorationColors(deco, underline, overline, linethrough, true, true);
1088 // Use a special function for underlines to get the positioning exactly right.
1089 bool isPrinting = textRenderer()->document().printing();
1091 bool linesAreOpaque = !isPrinting && (!(deco & TextDecorationUnderline) || underline.alpha() == 255) && (!(deco & TextDecorationOverline) || overline.alpha() == 255) && (!(deco & TextDecorationLineThrough) || linethrough.alpha() == 255);
1093 RenderStyle* styleToUse = renderer()->style(isFirstLineStyle());
1094 int baseline = styleToUse->fontMetrics().ascent();
1096 size_t shadowCount = shadowList ? shadowList->shadows().size() : 0;
1097 // Set the thick of the line to be 10% (or something else ?)of the computed font size and not less than 1px.
1098 // Using computedFontSize should take care of zoom as well.
1099 const float textDecorationThickness = std::max(1.f, styleToUse->computedFontSize() / 10.f);
1100 context->setStrokeThickness(textDecorationThickness);
1102 int extraOffset = 0;
1103 if (!linesAreOpaque && shadowCount > 1) {
1104 FloatRect clipRect(localOrigin, FloatSize(width, baseline + 2));
1105 for (size_t i = shadowCount; i--; ) {
1106 const ShadowData& s = shadowList->shadows()[i];
1107 FloatRect shadowRect(localOrigin, FloatSize(width, baseline + 2));
1108 shadowRect.inflate(s.blur());
1109 int shadowX = isHorizontal() ? s.x() : s.y();
1110 int shadowY = isHorizontal() ? s.y() : -s.x();
1111 shadowRect.move(shadowX, shadowY);
1112 clipRect.unite(shadowRect);
1113 extraOffset = max(extraOffset, max(0, shadowY) + s.blur());
1115 context->clip(clipRect);
1116 extraOffset += baseline + 2;
1117 localOrigin.move(0, extraOffset);
1120 for (size_t i = max(static_cast<size_t>(1), shadowCount); i--; ) {
1121 // Even if we have no shadows, we still want to run the code below this once.
1122 if (i < shadowCount) {
1124 // The last set of lines paints normally inside the clip.
1125 localOrigin.move(0, -extraOffset);
1128 const ShadowData& shadow = shadowList->shadows()[i];
1129 int shadowX = isHorizontal() ? shadow.x() : shadow.y();
1130 int shadowY = isHorizontal() ? shadow.y() : -shadow.x();
1131 context->setShadow(FloatSize(shadowX, shadowY - extraOffset), shadow.blur(), shadow.color());
1134 // Offset between lines - always non-zero, so lines never cross each other.
1135 float doubleOffset = textDecorationThickness + 1.f;
1136 context->setStrokeStyle(textDecorationStyleToStrokeStyle(decorationStyle));
1137 if (deco & TextDecorationUnderline) {
1138 context->setStrokeColor(underline);
1139 const int underlineOffset = computeUnderlineOffset(styleToUse->textUnderlinePosition(), styleToUse->fontMetrics(), this, textDecorationThickness);
1140 switch (decorationStyle) {
1141 case TextDecorationStyleWavy: {
1142 FloatPoint start(localOrigin.x(), localOrigin.y() + underlineOffset + doubleOffset);
1143 FloatPoint end(localOrigin.x() + width, localOrigin.y() + underlineOffset + doubleOffset);
1144 strokeWavyTextDecoration(context, start, end, textDecorationThickness);
1148 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset), width, isPrinting);
1150 if (decorationStyle == TextDecorationStyleDouble)
1151 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset + doubleOffset), width, isPrinting);
1154 if (deco & TextDecorationOverline) {
1155 context->setStrokeColor(overline);
1156 switch (decorationStyle) {
1157 case TextDecorationStyleWavy: {
1158 FloatPoint start(localOrigin.x(), localOrigin.y() - doubleOffset);
1159 FloatPoint end(localOrigin.x() + width, localOrigin.y() - doubleOffset);
1160 strokeWavyTextDecoration(context, start, end, textDecorationThickness);
1164 context->drawLineForText(localOrigin, width, isPrinting);
1165 if (decorationStyle == TextDecorationStyleDouble)
1166 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() - doubleOffset), width, isPrinting);
1169 if (deco & TextDecorationLineThrough) {
1170 context->setStrokeColor(linethrough);
1171 switch (decorationStyle) {
1172 case TextDecorationStyleWavy: {
1173 FloatPoint start(localOrigin.x(), localOrigin.y() + 2 * baseline / 3);
1174 FloatPoint end(localOrigin.x() + width, localOrigin.y() + 2 * baseline / 3);
1175 strokeWavyTextDecoration(context, start, end, textDecorationThickness);
1179 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + 2 * baseline / 3), width, isPrinting);
1180 if (decorationStyle == TextDecorationStyleDouble)
1181 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + doubleOffset + 2 * baseline / 3), width, isPrinting);
1187 static GraphicsContext::DocumentMarkerLineStyle lineStyleForMarkerType(DocumentMarker::MarkerType markerType)
1189 switch (markerType) {
1190 case DocumentMarker::Spelling:
1191 return GraphicsContext::DocumentMarkerSpellingLineStyle;
1192 case DocumentMarker::Grammar:
1193 return GraphicsContext::DocumentMarkerGrammarLineStyle;
1195 ASSERT_NOT_REACHED();
1196 return GraphicsContext::DocumentMarkerSpellingLineStyle;
1200 void InlineTextBox::paintDocumentMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, RenderStyle* style, const Font& font, bool grammar)
1202 // Never print spelling/grammar markers (5327887)
1203 if (textRenderer()->document().printing())
1206 if (m_truncation == cFullTruncation)
1209 float start = 0; // start of line to draw, relative to tx
1210 float width = m_logicalWidth; // how much line to draw
1212 // Determine whether we need to measure text
1213 bool markerSpansWholeBox = true;
1214 if (m_start <= (int)marker->startOffset())
1215 markerSpansWholeBox = false;
1216 if ((end() + 1) != marker->endOffset()) // end points at the last char, not past it
1217 markerSpansWholeBox = false;
1218 if (m_truncation != cNoTruncation)
1219 markerSpansWholeBox = false;
1221 if (!markerSpansWholeBox || grammar) {
1222 int startPosition = max<int>(marker->startOffset() - m_start, 0);
1223 int endPosition = min<int>(marker->endOffset() - m_start, m_len);
1225 if (m_truncation != cNoTruncation)
1226 endPosition = min<int>(endPosition, m_truncation);
1228 // Calculate start & width
1229 int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1230 int selHeight = selectionHeight();
1231 FloatPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY);
1232 TextRun run = constructTextRun(style, font);
1234 // FIXME: Convert the document markers to float rects.
1235 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, selHeight, startPosition, endPosition));
1236 start = markerRect.x() - startPoint.x();
1237 width = markerRect.width();
1239 // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to
1240 // display a toolTip. We don't do this for misspelling markers.
1242 markerRect.move(-boxOrigin.x(), -boxOrigin.y());
1243 markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1244 toRenderedDocumentMarker(marker)->setRenderedRect(markerRect);
1248 // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to
1249 // make sure to fit within those bounds. This means the top pixel(s) of the underline will overlap the
1250 // bottom pixel(s) of the glyphs in smaller font sizes. The alternatives are to increase the line spacing (bad!!)
1251 // or decrease the underline thickness. The overlap is actually the most useful, and matches what AppKit does.
1252 // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so
1253 // we pin to two pixels under the baseline.
1254 int lineThickness = misspellingLineThickness;
1255 int baseline = renderer()->style(isFirstLineStyle())->fontMetrics().ascent();
1256 int descent = logicalHeight() - baseline;
1257 int underlineOffset;
1258 if (descent <= (2 + lineThickness)) {
1259 // Place the underline at the very bottom of the text in small/medium fonts.
1260 underlineOffset = logicalHeight() - lineThickness;
1262 // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
1263 underlineOffset = baseline + 2;
1265 pt->drawLineForDocumentMarker(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, lineStyleForMarkerType(marker->type()));
1268 void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, RenderStyle* style, const Font& font)
1270 // Use same y positioning and height as for selection, so that when the selection and this highlight are on
1271 // the same word there are no pieces sticking out.
1272 int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1273 int selHeight = selectionHeight();
1275 int sPos = max(marker->startOffset() - m_start, (unsigned)0);
1276 int ePos = min(marker->endOffset() - m_start, (unsigned)m_len);
1277 TextRun run = constructTextRun(style, font);
1279 // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates.
1280 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, IntPoint(x(), selectionTop()), selHeight, sPos, ePos));
1281 markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1282 toRenderedDocumentMarker(marker)->setRenderedRect(markerRect);
1284 // Optionally highlight the text
1285 if (renderer()->frame()->editor().markedTextMatchesAreHighlighted()) {
1286 Color color = marker->activeMatch() ?
1287 RenderTheme::theme().platformActiveTextSearchHighlightColor() :
1288 RenderTheme::theme().platformInactiveTextSearchHighlightColor();
1289 GraphicsContextStateSaver stateSaver(*pt);
1290 updateGraphicsContext(pt, color, color, 0); // Don't draw text at all!
1291 pt->clip(FloatRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selHeight));
1292 pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, sPos, ePos);
1296 void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, bool background)
1298 if (!renderer()->node())
1301 Vector<DocumentMarker*> markers = renderer()->document().markers()->markersFor(renderer()->node());
1302 Vector<DocumentMarker*>::const_iterator markerIt = markers.begin();
1304 // Give any document markers that touch this run a chance to draw before the text has been drawn.
1305 // Note end() points at the last char, not one past it like endOffset and ranges do.
1306 for ( ; markerIt != markers.end(); markerIt++) {
1307 DocumentMarker* marker = *markerIt;
1309 // Paint either the background markers or the foreground markers, but not both
1310 switch (marker->type()) {
1311 case DocumentMarker::Grammar:
1312 case DocumentMarker::Spelling:
1316 case DocumentMarker::TextMatch:
1324 if (marker->endOffset() <= start())
1325 // marker is completely before this run. This might be a marker that sits before the
1326 // first run we draw, or markers that were within runs we skipped due to truncation.
1329 if (marker->startOffset() > end())
1330 // marker is completely after this run, bail. A later run will paint it.
1333 // marker intersects this run. Paint it.
1334 switch (marker->type()) {
1335 case DocumentMarker::Spelling:
1336 paintDocumentMarker(pt, boxOrigin, marker, style, font, false);
1338 case DocumentMarker::Grammar:
1339 paintDocumentMarker(pt, boxOrigin, marker, style, font, true);
1341 case DocumentMarker::TextMatch:
1342 paintTextMatchMarker(pt, boxOrigin, marker, style, font);
1345 ASSERT_NOT_REACHED();
1351 void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const FloatPoint& boxOrigin, const CompositionUnderline& underline)
1353 if (m_truncation == cFullTruncation)
1356 float start = 0; // start of line to draw, relative to tx
1357 float width = m_logicalWidth; // how much line to draw
1358 bool useWholeWidth = true;
1359 unsigned paintStart = m_start;
1360 unsigned paintEnd = end() + 1; // end points at the last char, not past it
1361 if (paintStart <= underline.startOffset) {
1362 paintStart = underline.startOffset;
1363 useWholeWidth = false;
1364 start = toRenderText(renderer())->width(m_start, paintStart - m_start, textPos(), isFirstLineStyle());
1366 if (paintEnd != underline.endOffset) { // end points at the last char, not past it
1367 paintEnd = min(paintEnd, (unsigned)underline.endOffset);
1368 useWholeWidth = false;
1370 if (m_truncation != cNoTruncation) {
1371 paintEnd = min(paintEnd, (unsigned)m_start + m_truncation);
1372 useWholeWidth = false;
1374 if (!useWholeWidth) {
1375 width = toRenderText(renderer())->width(paintStart, paintEnd - paintStart, textPos() + start, isFirstLineStyle());
1378 // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
1379 // All other marked text underlines are 1px thick.
1380 // If there's not enough space the underline will touch or overlap characters.
1381 int lineThickness = 1;
1382 int baseline = renderer()->style(isFirstLineStyle())->fontMetrics().ascent();
1383 if (underline.thick && logicalHeight() - baseline >= 2)
1386 // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
1387 // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
1391 ctx->setStrokeColor(underline.color);
1392 ctx->setStrokeThickness(lineThickness);
1393 ctx->drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, textRenderer()->document().printing());
1396 int InlineTextBox::caretMinOffset() const
1401 int InlineTextBox::caretMaxOffset() const
1403 return m_start + m_len;
1406 float InlineTextBox::textPos() const
1408 // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset
1409 // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width.
1410 if (logicalLeft() == 0)
1412 return logicalLeft() - root()->logicalLeft();
1415 int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs) const
1420 if (lineOffset - logicalLeft() > logicalWidth())
1421 return isLeftToRightDirection() ? len() : 0;
1422 if (lineOffset - logicalLeft() < 0)
1423 return isLeftToRightDirection() ? 0 : len();
1425 FontCachePurgePreventer fontCachePurgePreventer;
1427 RenderText* text = toRenderText(renderer());
1428 RenderStyle* style = text->style(isFirstLineStyle());
1429 const Font& font = style->font();
1430 return font.offsetForPosition(constructTextRun(style, font), lineOffset - logicalLeft(), includePartialGlyphs);
1433 float InlineTextBox::positionForOffset(int offset) const
1435 ASSERT(offset >= m_start);
1436 ASSERT(offset <= m_start + m_len);
1439 return logicalLeft();
1441 FontCachePurgePreventer fontCachePurgePreventer;
1443 RenderText* text = toRenderText(renderer());
1444 RenderStyle* styleToUse = text->style(isFirstLineStyle());
1446 const Font& font = styleToUse->font();
1447 int from = !isLeftToRightDirection() ? offset - m_start : 0;
1448 int to = !isLeftToRightDirection() ? m_len : offset - m_start;
1449 // FIXME: Do we need to add rightBearing here?
1450 return font.selectionRectForText(constructTextRun(styleToUse, font), IntPoint(logicalLeft(), 0), 0, from, to).maxX();
1453 bool InlineTextBox::containsCaretOffset(int offset) const
1455 // Offsets before the box are never "in".
1456 if (offset < m_start)
1459 int pastEnd = m_start + m_len;
1461 // Offsets inside the box (not at either edge) are always "in".
1462 if (offset < pastEnd)
1465 // Offsets outside the box are always "out".
1466 if (offset > pastEnd)
1469 // Offsets at the end are "out" for line breaks (they are on the next line).
1473 // Offsets at the end are "in" for normal boxes (but the caller has to check affinity).
1477 void InlineTextBox::characterWidths(Vector<float>& widths) const
1479 FontCachePurgePreventer fontCachePurgePreventer;
1481 RenderText* textObj = textRenderer();
1482 RenderStyle* styleToUse = textObj->style(isFirstLineStyle());
1483 const Font& font = styleToUse->font();
1485 TextRun textRun = constructTextRun(styleToUse, font);
1487 GlyphBuffer glyphBuffer;
1488 WidthIterator it(&font, textRun);
1489 float lastWidth = 0;
1490 widths.resize(m_len);
1491 for (unsigned i = 0; i < m_len; i++) {
1492 it.advance(i + 1, &glyphBuffer);
1493 widths[i] = it.m_runWidthSoFar - lastWidth;
1494 lastWidth = it.m_runWidthSoFar;
1498 TextRun InlineTextBox::constructTextRun(RenderStyle* style, const Font& font, StringBuilder* charactersWithHyphen) const
1502 RenderText* textRenderer = this->textRenderer();
1503 ASSERT(textRenderer);
1504 ASSERT(textRenderer->text());
1506 StringView string = textRenderer->text().createView();
1507 unsigned startPos = start();
1508 unsigned length = len();
1510 if (string.length() != length || startPos)
1511 string.narrow(startPos, length);
1513 return constructTextRun(style, font, string, textRenderer->textLength() - startPos, charactersWithHyphen);
1516 TextRun InlineTextBox::constructTextRun(RenderStyle* style, const Font& font, StringView string, int maximumLength, StringBuilder* charactersWithHyphen) const
1520 RenderText* textRenderer = this->textRenderer();
1521 ASSERT(textRenderer);
1523 if (charactersWithHyphen) {
1524 const AtomicString& hyphenString = style->hyphenString();
1525 charactersWithHyphen->reserveCapacity(string.length() + hyphenString.length());
1526 charactersWithHyphen->append(string);
1527 charactersWithHyphen->append(hyphenString);
1528 string = charactersWithHyphen->toString().createView();
1529 maximumLength = string.length();
1532 ASSERT(maximumLength >= static_cast<int>(string.length()));
1534 TextRun run(string, textPos(), expansion(), expansionBehavior(), direction(), dirOverride() || style->rtlOrdering() == VisualOrder, !textRenderer->canUseSimpleFontCodePath());
1535 run.setTabSize(!style->collapseWhiteSpace(), style->tabSize());
1536 if (textRunNeedsRenderingContext(font))
1537 run.setRenderingContext(SVGTextRunRenderingContext::create(textRenderer));
1539 // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring.
1540 run.setCharactersLength(maximumLength);
1541 ASSERT(run.charactersLength() >= run.length());
1545 TextRun InlineTextBox::constructTextRunForInspector(RenderStyle* style, const Font& font) const
1547 return InlineTextBox::constructTextRun(style, font);
1552 const char* InlineTextBox::boxName() const
1554 return "InlineTextBox";
1557 void InlineTextBox::showBox(int printedCharacters) const
1559 const RenderText* obj = toRenderText(renderer());
1560 String value = obj->text();
1561 value = value.substring(start(), len());
1562 value.replaceWithLiteral('\\', "\\\\");
1563 value.replaceWithLiteral('\n', "\\n");
1564 printedCharacters += fprintf(stderr, "%s\t%p", boxName(), this);
1565 for (; printedCharacters < showTreeCharacterOffset; printedCharacters++)
1567 printedCharacters = fprintf(stderr, "\t%s %p", obj->renderName(), obj);
1568 const int rendererCharacterOffset = 24;
1569 for (; printedCharacters < rendererCharacterOffset; printedCharacters++)
1571 fprintf(stderr, "(%d,%d) \"%s\"\n", start(), start() + len(), value.utf8().data());
1576 } // namespace WebCore