• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * (C) 1999 Lars Knoll (knoll@kde.org)
3  * (C) 2000 Dirk Mueller (mueller@kde.org)
4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "config.h"
24 #include "InlineTextBox.h"
25 
26 #include "Chrome.h"
27 #include "ChromeClient.h"
28 #include "Document.h"
29 #include "DocumentMarkerController.h"
30 #include "Editor.h"
31 #include "EllipsisBox.h"
32 #include "Frame.h"
33 #include "GraphicsContext.h"
34 #include "HitTestResult.h"
35 #include "Page.h"
36 #include "PaintInfo.h"
37 #include "RenderArena.h"
38 #include "RenderBlock.h"
39 #include "RenderCombineText.h"
40 #include "RenderRubyRun.h"
41 #include "RenderRubyText.h"
42 #include "RenderTheme.h"
43 #include "Text.h"
44 #include "break_lines.h"
45 #include <wtf/AlwaysInline.h>
46 #include <wtf/text/CString.h>
47 
48 using namespace std;
49 
50 namespace WebCore {
51 
52 typedef WTF::HashMap<const InlineTextBox*, IntRect> InlineTextBoxOverflowMap;
53 static InlineTextBoxOverflowMap* gTextBoxesWithOverflow;
54 
destroy(RenderArena * arena)55 void InlineTextBox::destroy(RenderArena* arena)
56 {
57     if (!m_knownToHaveNoOverflow && gTextBoxesWithOverflow)
58         gTextBoxesWithOverflow->remove(this);
59     InlineBox::destroy(arena);
60 }
61 
logicalOverflowRect() const62 IntRect InlineTextBox::logicalOverflowRect() const
63 {
64     if (m_knownToHaveNoOverflow || !gTextBoxesWithOverflow)
65         return enclosingIntRect(logicalFrameRect());
66     return gTextBoxesWithOverflow->get(this);
67 }
68 
setLogicalOverflowRect(const IntRect & rect)69 void InlineTextBox::setLogicalOverflowRect(const IntRect& rect)
70 {
71     ASSERT(!m_knownToHaveNoOverflow);
72     if (!gTextBoxesWithOverflow)
73         gTextBoxesWithOverflow = new InlineTextBoxOverflowMap;
74     gTextBoxesWithOverflow->add(this, rect);
75 }
76 
baselinePosition(FontBaseline baselineType) const77 int InlineTextBox::baselinePosition(FontBaseline baselineType) const
78 {
79     if (!isText() || !parent())
80         return 0;
81     if (parent()->renderer() == renderer()->parent())
82         return parent()->baselinePosition(baselineType);
83     return toRenderBoxModelObject(renderer()->parent())->baselinePosition(baselineType, m_firstLine, isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
84 }
85 
lineHeight() const86 int InlineTextBox::lineHeight() const
87 {
88     if (!isText() || !renderer()->parent())
89         return 0;
90     if (m_renderer->isBR())
91         return toRenderBR(m_renderer)->lineHeight(m_firstLine);
92     if (parent()->renderer() == renderer()->parent())
93         return parent()->lineHeight();
94     return toRenderBoxModelObject(renderer()->parent())->lineHeight(m_firstLine, isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
95 }
96 
selectionTop()97 int InlineTextBox::selectionTop()
98 {
99     return root()->selectionTop();
100 }
101 
selectionBottom()102 int InlineTextBox::selectionBottom()
103 {
104     return root()->selectionBottom();
105 }
106 
selectionHeight()107 int InlineTextBox::selectionHeight()
108 {
109     return root()->selectionHeight();
110 }
111 
isSelected(int startPos,int endPos) const112 bool InlineTextBox::isSelected(int startPos, int endPos) const
113 {
114     int sPos = max(startPos - m_start, 0);
115     int ePos = min(endPos - m_start, (int)m_len);
116     return (sPos < ePos);
117 }
118 
selectionState()119 RenderObject::SelectionState InlineTextBox::selectionState()
120 {
121     RenderObject::SelectionState state = renderer()->selectionState();
122     if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
123         int startPos, endPos;
124         renderer()->selectionStartEnd(startPos, endPos);
125         // The position after a hard line break is considered to be past its end.
126         int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0);
127 
128         bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
129         bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
130         if (start && end)
131             state = RenderObject::SelectionBoth;
132         else if (start)
133             state = RenderObject::SelectionStart;
134         else if (end)
135             state = RenderObject::SelectionEnd;
136         else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
137                  (state == RenderObject::SelectionStart || endPos > lastSelectable))
138             state = RenderObject::SelectionInside;
139         else if (state == RenderObject::SelectionBoth)
140             state = RenderObject::SelectionNone;
141     }
142 
143     // If there are ellipsis following, make sure their selection is updated.
144     if (m_truncation != cNoTruncation && root()->ellipsisBox()) {
145         EllipsisBox* ellipsis = root()->ellipsisBox();
146         if (state != RenderObject::SelectionNone) {
147             int start, end;
148             selectionStartEnd(start, end);
149             // The ellipsis should be considered to be selected if the end of
150             // the selection is past the beginning of the truncation and the
151             // beginning of the selection is before or at the beginning of the
152             // truncation.
153             ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ?
154                 RenderObject::SelectionInside : RenderObject::SelectionNone);
155         } else
156             ellipsis->setSelectionState(RenderObject::SelectionNone);
157     }
158 
159     return state;
160 }
161 
162 typedef Vector<UChar, 256> BufferForAppendingHyphen;
163 
adjustCharactersAndLengthForHyphen(BufferForAppendingHyphen & charactersWithHyphen,RenderStyle * style,const UChar * & characters,int & length)164 static void adjustCharactersAndLengthForHyphen(BufferForAppendingHyphen& charactersWithHyphen, RenderStyle* style, const UChar*& characters, int& length)
165 {
166     const AtomicString& hyphenString = style->hyphenString();
167     charactersWithHyphen.reserveCapacity(length + hyphenString.length());
168     charactersWithHyphen.append(characters, length);
169     charactersWithHyphen.append(hyphenString.characters(), hyphenString.length());
170     characters = charactersWithHyphen.data();
171     length += hyphenString.length();
172 }
173 
selectionRect(int tx,int ty,int startPos,int endPos)174 IntRect InlineTextBox::selectionRect(int tx, int ty, int startPos, int endPos)
175 {
176     int sPos = max(startPos - m_start, 0);
177     int ePos = min(endPos - m_start, (int)m_len);
178 
179     if (sPos > ePos)
180         return IntRect();
181 
182     RenderText* textObj = textRenderer();
183     int selTop = selectionTop();
184     int selHeight = selectionHeight();
185     RenderStyle* styleToUse = textObj->style(m_firstLine);
186     const Font& f = styleToUse->font();
187 
188     const UChar* characters = textObj->text()->characters() + m_start;
189     int len = m_len;
190     BufferForAppendingHyphen charactersWithHyphen;
191     if (ePos == len && hasHyphen()) {
192         adjustCharactersAndLengthForHyphen(charactersWithHyphen, styleToUse, characters, len);
193         ePos = len;
194     }
195 
196     IntRect r = enclosingIntRect(f.selectionRectForText(TextRun(characters, len, textObj->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride),
197                                                         IntPoint(), selHeight, sPos, ePos));
198 
199     int logicalWidth = r.width();
200     if (r.x() > m_logicalWidth)
201         logicalWidth  = 0;
202     else if (r.maxX() > m_logicalWidth)
203         logicalWidth = m_logicalWidth - r.x();
204 
205     IntPoint topPoint = isHorizontal() ? IntPoint(tx + m_x + r.x(), ty + selTop) : IntPoint(tx + selTop, ty + m_y + r.x());
206     int width = isHorizontal() ? logicalWidth : selHeight;
207     int height = isHorizontal() ? selHeight : logicalWidth;
208 
209     return IntRect(topPoint, IntSize(width, height));
210 }
211 
deleteLine(RenderArena * arena)212 void InlineTextBox::deleteLine(RenderArena* arena)
213 {
214     toRenderText(renderer())->removeTextBox(this);
215     destroy(arena);
216 }
217 
extractLine()218 void InlineTextBox::extractLine()
219 {
220     if (m_extracted)
221         return;
222 
223     toRenderText(renderer())->extractTextBox(this);
224 }
225 
attachLine()226 void InlineTextBox::attachLine()
227 {
228     if (!m_extracted)
229         return;
230 
231     toRenderText(renderer())->attachTextBox(this);
232 }
233 
placeEllipsisBox(bool flowIsLTR,float visibleLeftEdge,float visibleRightEdge,float ellipsisWidth,bool & foundBox)234 float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, bool& foundBox)
235 {
236     if (foundBox) {
237         m_truncation = cFullTruncation;
238         return -1;
239     }
240 
241     // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
242     float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
243 
244     // Criteria for full truncation:
245     // LTR: the left edge of the ellipsis is to the left of our text run.
246     // RTL: the right edge of the ellipsis is to the right of our text run.
247     bool ltrFullTruncation = flowIsLTR && ellipsisX <= m_x;
248     bool rtlFullTruncation = !flowIsLTR && ellipsisX >= (m_x + m_logicalWidth);
249     if (ltrFullTruncation || rtlFullTruncation) {
250         // Too far.  Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
251         m_truncation = cFullTruncation;
252         foundBox = true;
253         return -1;
254     }
255 
256     bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < m_x + m_logicalWidth);
257     bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > m_x);
258     if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
259         foundBox = true;
260 
261         // The inline box may have different directionality than it's parent.  Since truncation
262         // behavior depends both on both the parent and the inline block's directionality, we
263         // must keep track of these separately.
264         bool ltr = isLeftToRightDirection();
265         if (ltr != flowIsLTR) {
266           // Width in pixels of the visible portion of the box, excluding the ellipsis.
267           int visibleBoxWidth = visibleRightEdge - visibleLeftEdge  - ellipsisWidth;
268           ellipsisX = ltr ? m_x + visibleBoxWidth : m_x + m_logicalWidth - visibleBoxWidth;
269         }
270 
271         int offset = offsetForPosition(ellipsisX, false);
272         if (offset == 0) {
273             // No characters should be rendered.  Set ourselves to full truncation and place the ellipsis at the min of our start
274             // and the ellipsis edge.
275             m_truncation = cFullTruncation;
276             return min(ellipsisX, m_x);
277         }
278 
279         // Set the truncation index on the text run.
280         m_truncation = offset;
281 
282         // If we got here that means that we were only partially truncated and we need to return the pixel offset at which
283         // to place the ellipsis.
284         float widthOfVisibleText = toRenderText(renderer())->width(m_start, offset, textPos(), m_firstLine);
285 
286         // The ellipsis needs to be placed just after the last visible character.
287         // Where "after" is defined by the flow directionality, not the inline
288         // box directionality.
289         // e.g. In the case of an LTR inline box truncated in an RTL flow then we can
290         // have a situation such as |Hello| -> |...He|
291         if (flowIsLTR)
292             return m_x + widthOfVisibleText;
293         else
294             return (m_x + m_logicalWidth) - widthOfVisibleText - ellipsisWidth;
295     }
296     return -1;
297 }
298 
correctedTextColor(Color textColor,Color backgroundColor)299 Color correctedTextColor(Color textColor, Color backgroundColor)
300 {
301     // Adjust the text color if it is too close to the background color,
302     // by darkening or lightening it to move it further away.
303 
304     int d = differenceSquared(textColor, backgroundColor);
305     // semi-arbitrarily chose 65025 (255^2) value here after a few tests;
306     if (d > 65025) {
307         return textColor;
308     }
309 
310     int distanceFromWhite = differenceSquared(textColor, Color::white);
311     int distanceFromBlack = differenceSquared(textColor, Color::black);
312 
313     if (distanceFromWhite < distanceFromBlack) {
314         return textColor.dark();
315     }
316 
317     return textColor.light();
318 }
319 
updateGraphicsContext(GraphicsContext * context,const Color & fillColor,const Color & strokeColor,float strokeThickness,ColorSpace colorSpace)320 void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness, ColorSpace colorSpace)
321 {
322     TextDrawingModeFlags mode = context->textDrawingMode();
323     if (strokeThickness > 0) {
324         TextDrawingModeFlags newMode = mode | TextModeStroke;
325         if (mode != newMode) {
326             context->setTextDrawingMode(newMode);
327             mode = newMode;
328         }
329     }
330 
331     if (mode & TextModeFill && (fillColor != context->fillColor() || colorSpace != context->fillColorSpace()))
332         context->setFillColor(fillColor, colorSpace);
333 
334     if (mode & TextModeStroke) {
335         if (strokeColor != context->strokeColor())
336             context->setStrokeColor(strokeColor, colorSpace);
337         if (strokeThickness != context->strokeThickness())
338             context->setStrokeThickness(strokeThickness);
339     }
340 }
341 
isLineBreak() const342 bool InlineTextBox::isLineBreak() const
343 {
344     return renderer()->isBR() || (renderer()->style()->preserveNewline() && len() == 1 && (*textRenderer()->text())[start()] == '\n');
345 }
346 
nodeAtPoint(const HitTestRequest &,HitTestResult & result,int x,int y,int tx,int ty,int,int)347 bool InlineTextBox::nodeAtPoint(const HitTestRequest&, HitTestResult& result, int x, int y, int tx, int ty, int /* lineTop */, int /*lineBottom*/)
348 {
349     if (isLineBreak())
350         return false;
351 
352     FloatPoint boxOrigin = locationIncludingFlipping();
353     boxOrigin.move(tx, ty);
354     FloatRect rect(boxOrigin, IntSize(width(), height()));
355     if (m_truncation != cFullTruncation && visibleToHitTesting() && rect.intersects(result.rectForPoint(x, y))) {
356         renderer()->updateHitTestResult(result, flipForWritingMode(IntPoint(x - tx, y - ty)));
357         if (!result.addNodeToRectBasedTestResult(renderer()->node(), x, y, rect))
358             return true;
359     }
360     return false;
361 }
362 
applyShadowToGraphicsContext(GraphicsContext * context,const ShadowData * shadow,const FloatRect & textRect,bool stroked,bool opaque,bool horizontal)363 FloatSize InlineTextBox::applyShadowToGraphicsContext(GraphicsContext* context, const ShadowData* shadow, const FloatRect& textRect, bool stroked, bool opaque, bool horizontal)
364 {
365     if (!shadow)
366         return FloatSize();
367 
368     FloatSize extraOffset;
369     int shadowX = horizontal ? shadow->x() : shadow->y();
370     int shadowY = horizontal ? shadow->y() : -shadow->x();
371     FloatSize shadowOffset(shadowX, shadowY);
372     int shadowBlur = shadow->blur();
373     const Color& shadowColor = shadow->color();
374 
375     if (shadow->next() || stroked || !opaque) {
376         FloatRect shadowRect(textRect);
377         shadowRect.inflate(shadowBlur);
378         shadowRect.move(shadowOffset);
379         context->save();
380         context->clip(shadowRect);
381 
382         extraOffset = FloatSize(0, 2 * textRect.height() + max(0.0f, shadowOffset.height()) + shadowBlur);
383         shadowOffset -= extraOffset;
384     }
385 
386     context->setShadow(shadowOffset, shadowBlur, shadowColor, context->fillColorSpace());
387     return extraOffset;
388 }
389 
paintTextWithShadows(GraphicsContext * context,const Font & font,const TextRun & textRun,const AtomicString & emphasisMark,int emphasisMarkOffset,int startOffset,int endOffset,int truncationPoint,const FloatPoint & textOrigin,const FloatRect & boxRect,const ShadowData * shadow,bool stroked,bool horizontal)390 static void paintTextWithShadows(GraphicsContext* context, const Font& font, const TextRun& textRun, const AtomicString& emphasisMark, int emphasisMarkOffset, int startOffset, int endOffset, int truncationPoint, const FloatPoint& textOrigin,
391                                  const FloatRect& boxRect, const ShadowData* shadow, bool stroked, bool horizontal)
392 {
393     Color fillColor = context->fillColor();
394     ColorSpace fillColorSpace = context->fillColorSpace();
395     bool opaque = fillColor.alpha() == 255;
396     if (!opaque)
397         context->setFillColor(Color::black, fillColorSpace);
398 
399     do {
400         IntSize extraOffset;
401         if (shadow)
402             extraOffset = roundedIntSize(InlineTextBox::applyShadowToGraphicsContext(context, shadow, boxRect, stroked, opaque, horizontal));
403         else if (!opaque)
404             context->setFillColor(fillColor, fillColorSpace);
405 
406         if (startOffset <= endOffset) {
407             if (emphasisMark.isEmpty())
408                 context->drawText(font, textRun, textOrigin + extraOffset, startOffset, endOffset);
409             else
410                 context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset), startOffset, endOffset);
411         } else {
412             if (endOffset > 0) {
413                 if (emphasisMark.isEmpty())
414                     context->drawText(font, textRun, textOrigin + extraOffset,  0, endOffset);
415                 else
416                     context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset),  0, endOffset);
417             } if (startOffset < truncationPoint) {
418                 if (emphasisMark.isEmpty())
419                     context->drawText(font, textRun, textOrigin + extraOffset, startOffset, truncationPoint);
420                 else
421                     context->drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + extraOffset + IntSize(0, emphasisMarkOffset),  startOffset, truncationPoint);
422             }
423         }
424 
425         if (!shadow)
426             break;
427 
428         if (shadow->next() || stroked || !opaque)
429             context->restore();
430         else
431             context->clearShadow();
432 
433         shadow = shadow->next();
434     } while (shadow || stroked || !opaque);
435 }
436 
getEmphasisMarkPosition(RenderStyle * style,TextEmphasisPosition & emphasisPosition) const437 bool InlineTextBox::getEmphasisMarkPosition(RenderStyle* style, TextEmphasisPosition& emphasisPosition) const
438 {
439     // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
440     if (style->textEmphasisMark() == TextEmphasisMarkNone)
441         return false;
442 
443     emphasisPosition = style->textEmphasisPosition();
444     if (emphasisPosition == TextEmphasisPositionUnder)
445         return true; // Ruby text is always over, so it cannot suppress emphasis marks under.
446 
447     RenderBlock* containingBlock = renderer()->containingBlock();
448     if (!containingBlock->isRubyBase())
449         return true; // This text is not inside a ruby base, so it does not have ruby text over it.
450 
451     if (!containingBlock->parent()->isRubyRun())
452         return true; // Cannot get the ruby text.
453 
454     RenderRubyText* rubyText = static_cast<RenderRubyRun*>(containingBlock->parent())->rubyText();
455 
456     // The emphasis marks over are suppressed only if there is a ruby text box and it not empty.
457     return !rubyText || !rubyText->firstLineBox();
458 }
459 
460 enum RotationDirection { Counterclockwise, Clockwise };
461 
rotation(const FloatRect & boxRect,RotationDirection clockwise)462 static inline AffineTransform rotation(const FloatRect& boxRect, RotationDirection clockwise)
463 {
464     return clockwise ? AffineTransform(0, 1, -1, 0, boxRect.x() + boxRect.maxY(), boxRect.maxY() - boxRect.x())
465         : AffineTransform(0, -1, 1, 0, boxRect.x() - boxRect.maxY(), boxRect.x() + boxRect.maxY());
466 }
467 
paint(PaintInfo & paintInfo,int tx,int ty,int,int)468 void InlineTextBox::paint(PaintInfo& paintInfo, int tx, int ty, int /*lineTop*/, int /*lineBottom*/)
469 {
470     if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(renderer()) || renderer()->style()->visibility() != VISIBLE ||
471         m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len)
472         return;
473 
474     ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
475 
476     int logicalLeftSide = logicalLeftVisualOverflow();
477     int logicalRightSide = logicalRightVisualOverflow();
478     int logicalStart = logicalLeftSide + (isHorizontal() ? tx : ty);
479     int logicalExtent = logicalRightSide - logicalLeftSide;
480 
481     int paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
482     int paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
483 
484     if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
485         return;
486 
487     bool isPrinting = textRenderer()->document()->printing();
488 
489     // Determine whether or not we're selected.
490     bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
491     if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
492         // When only painting the selection, don't bother to paint if there is none.
493         return;
494 
495     if (m_truncation != cNoTruncation) {
496         if (renderer()->containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) {
497             // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
498             // at which we start drawing text.
499             // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
500             // |Hello|CBA| -> |...He|CBA|
501             // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
502             // farther to the right.
503             // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
504             // truncated string i.e.  |Hello|CBA| -> |...lo|CBA|
505             int widthOfVisibleText = toRenderText(renderer())->width(m_start, m_truncation, textPos(), m_firstLine);
506             int widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
507             // FIXME: The hit testing logic also needs to take this translation int account.
508             if (isHorizontal())
509                 tx += isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText;
510             else
511                 ty += isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText;
512         }
513     }
514 
515     GraphicsContext* context = paintInfo.context;
516 
517     RenderStyle* styleToUse = renderer()->style(m_firstLine);
518 
519     ty -= styleToUse->isHorizontalWritingMode() ? 0 : logicalHeight();
520 
521     FloatPoint boxOrigin = locationIncludingFlipping();
522     boxOrigin.move(tx, ty);
523     FloatRect boxRect(boxOrigin, IntSize(logicalWidth(), logicalHeight()));
524 
525     RenderCombineText* combinedText = styleToUse->hasTextCombine() && textRenderer()->isCombineText() && toRenderCombineText(textRenderer())->isCombined() ? toRenderCombineText(textRenderer()) : 0;
526 
527     bool shouldRotate = !isHorizontal() && !combinedText;
528     if (shouldRotate)
529         context->concatCTM(rotation(boxRect, Clockwise));
530 
531     // Determine whether or not we have composition underlines to draw.
532     bool containsComposition = renderer()->node() && renderer()->frame()->editor()->compositionNode() == renderer()->node();
533     bool useCustomUnderlines = containsComposition && renderer()->frame()->editor()->compositionUsesCustomUnderlines();
534 
535     // Set our font.
536     const Font& font = styleToUse->font();
537 
538     FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());
539 
540     if (combinedText)
541         combinedText->adjustTextOrigin(textOrigin, boxRect);
542 
543     // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
544     // and composition underlines.
545     if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
546 #if PLATFORM(MAC)
547         // Custom highlighters go behind everything else.
548         if (styleToUse->highlight() != nullAtom && !context->paintingDisabled())
549             paintCustomHighlight(tx, ty, styleToUse->highlight());
550 #endif
551 
552         if (containsComposition && !useCustomUnderlines)
553             paintCompositionBackground(context, boxOrigin, styleToUse, font,
554                 renderer()->frame()->editor()->compositionStart(),
555                 renderer()->frame()->editor()->compositionEnd());
556 
557         paintDocumentMarkers(context, boxOrigin, styleToUse, font, true);
558 
559         if (haveSelection && !useCustomUnderlines)
560             paintSelection(context, boxOrigin, styleToUse, font);
561     }
562 
563     // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
564     Color textFillColor;
565     Color textStrokeColor;
566     Color emphasisMarkColor;
567     float textStrokeWidth = styleToUse->textStrokeWidth();
568     const ShadowData* textShadow = paintInfo.forceBlackText ? 0 : styleToUse->textShadow();
569 
570     if (paintInfo.forceBlackText) {
571         textFillColor = Color::black;
572         textStrokeColor = Color::black;
573         emphasisMarkColor = Color::black;
574     } else {
575         textFillColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextFillColor);
576 
577         // Make the text fill color legible against a white background
578         if (styleToUse->forceBackgroundsToWhite())
579             textFillColor = correctedTextColor(textFillColor, Color::white);
580 
581         textStrokeColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
582 
583         // Make the text stroke color legible against a white background
584         if (styleToUse->forceBackgroundsToWhite())
585             textStrokeColor = correctedTextColor(textStrokeColor, Color::white);
586 
587         emphasisMarkColor = styleToUse->visitedDependentColor(CSSPropertyWebkitTextEmphasisColor);
588 
589         // Make the text stroke color legible against a white background
590         if (styleToUse->forceBackgroundsToWhite())
591             emphasisMarkColor = correctedTextColor(emphasisMarkColor, Color::white);
592     }
593 
594     bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection);
595     bool paintSelectedTextSeparately = false;
596 
597     Color selectionFillColor = textFillColor;
598     Color selectionStrokeColor = textStrokeColor;
599     Color selectionEmphasisMarkColor = emphasisMarkColor;
600     float selectionStrokeWidth = textStrokeWidth;
601     const ShadowData* selectionShadow = textShadow;
602     if (haveSelection) {
603         // Check foreground color first.
604         Color foreground = paintInfo.forceBlackText ? Color::black : renderer()->selectionForegroundColor();
605         if (foreground.isValid() && foreground != selectionFillColor) {
606             if (!paintSelectedTextOnly)
607                 paintSelectedTextSeparately = true;
608             selectionFillColor = foreground;
609         }
610 
611         Color emphasisMarkForeground = paintInfo.forceBlackText ? Color::black : renderer()->selectionEmphasisMarkColor();
612         if (emphasisMarkForeground.isValid() && emphasisMarkForeground != selectionEmphasisMarkColor) {
613             if (!paintSelectedTextOnly)
614                 paintSelectedTextSeparately = true;
615             selectionEmphasisMarkColor = emphasisMarkForeground;
616         }
617 
618         if (RenderStyle* pseudoStyle = renderer()->getCachedPseudoStyle(SELECTION)) {
619             const ShadowData* shadow = paintInfo.forceBlackText ? 0 : pseudoStyle->textShadow();
620             if (shadow != selectionShadow) {
621                 if (!paintSelectedTextOnly)
622                     paintSelectedTextSeparately = true;
623                 selectionShadow = shadow;
624             }
625 
626             float strokeWidth = pseudoStyle->textStrokeWidth();
627             if (strokeWidth != selectionStrokeWidth) {
628                 if (!paintSelectedTextOnly)
629                     paintSelectedTextSeparately = true;
630                 selectionStrokeWidth = strokeWidth;
631             }
632 
633             Color stroke = paintInfo.forceBlackText ? Color::black : pseudoStyle->visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
634             if (stroke != selectionStrokeColor) {
635                 if (!paintSelectedTextOnly)
636                     paintSelectedTextSeparately = true;
637                 selectionStrokeColor = stroke;
638             }
639         }
640     }
641 
642     int length = m_len;
643     const UChar* characters;
644     if (!combinedText)
645         characters = textRenderer()->text()->characters() + m_start;
646     else
647         combinedText->charactersToRender(m_start, characters, length);
648 
649     BufferForAppendingHyphen charactersWithHyphen;
650     if (hasHyphen())
651         adjustCharactersAndLengthForHyphen(charactersWithHyphen, styleToUse, characters, length);
652 
653     TextRun textRun(characters, length, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride || styleToUse->visuallyOrdered());
654 
655     int sPos = 0;
656     int ePos = 0;
657     if (paintSelectedTextOnly || paintSelectedTextSeparately)
658         selectionStartEnd(sPos, ePos);
659 
660     if (m_truncation != cNoTruncation) {
661         sPos = min<int>(sPos, m_truncation);
662         ePos = min<int>(ePos, m_truncation);
663         length = m_truncation;
664     }
665 
666     int emphasisMarkOffset = 0;
667     TextEmphasisPosition emphasisMarkPosition;
668     bool hasTextEmphasis = getEmphasisMarkPosition(styleToUse, emphasisMarkPosition);
669     const AtomicString& emphasisMark = hasTextEmphasis ? styleToUse->textEmphasisMarkString() : nullAtom;
670     if (!emphasisMark.isEmpty())
671         emphasisMarkOffset = emphasisMarkPosition == TextEmphasisPositionOver ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
672 
673     if (!paintSelectedTextOnly) {
674         // For stroked painting, we have to change the text drawing mode.  It's probably dangerous to leave that mutated as a side
675         // effect, so only when we know we're stroking, do a save/restore.
676         if (textStrokeWidth > 0)
677             context->save();
678 
679         updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
680         if (!paintSelectedTextSeparately || ePos <= sPos) {
681             // FIXME: Truncate right-to-left text correctly.
682             paintTextWithShadows(context, font, textRun, nullAtom, 0, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
683         } else
684             paintTextWithShadows(context, font, textRun, nullAtom, 0, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
685 
686         if (!emphasisMark.isEmpty()) {
687             updateGraphicsContext(context, emphasisMarkColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
688 
689             static TextRun objectReplacementCharacterTextRun(&objectReplacementCharacter, 1);
690             TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
691             FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
692             if (combinedText)
693                 context->concatCTM(rotation(boxRect, Clockwise));
694 
695             if (!paintSelectedTextSeparately || ePos <= sPos) {
696                 // FIXME: Truncate right-to-left text correctly.
697                 paintTextWithShadows(context, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, 0, length, length, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
698             } else
699                 paintTextWithShadows(context, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, ePos, sPos, length, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
700 
701             if (combinedText)
702                 context->concatCTM(rotation(boxRect, Counterclockwise));
703         }
704 
705         if (textStrokeWidth > 0)
706             context->restore();
707     }
708 
709     if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) {
710         // paint only the text that is selected
711         if (selectionStrokeWidth > 0)
712             context->save();
713 
714         updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth, styleToUse->colorSpace());
715         paintTextWithShadows(context, font, textRun, nullAtom, 0, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
716         if (!emphasisMark.isEmpty()) {
717             updateGraphicsContext(context, selectionEmphasisMarkColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
718 
719             static TextRun objectReplacementCharacterTextRun(&objectReplacementCharacter, 1);
720             TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
721             FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
722             if (combinedText)
723                 context->concatCTM(rotation(boxRect, Clockwise));
724 
725             paintTextWithShadows(context, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, sPos, ePos, length, emphasisMarkTextOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
726 
727             if (combinedText)
728                 context->concatCTM(rotation(boxRect, Counterclockwise));
729         }
730         if (selectionStrokeWidth > 0)
731             context->restore();
732     }
733 
734     // Paint decorations
735     int textDecorations = styleToUse->textDecorationsInEffect();
736     if (textDecorations != TDNONE && paintInfo.phase != PaintPhaseSelection) {
737         updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth, styleToUse->colorSpace());
738         paintDecoration(context, boxOrigin, textDecorations, textShadow);
739     }
740 
741     if (paintInfo.phase == PaintPhaseForeground) {
742         paintDocumentMarkers(context, boxOrigin, styleToUse, font, false);
743 
744         if (useCustomUnderlines) {
745             const Vector<CompositionUnderline>& underlines = renderer()->frame()->editor()->customCompositionUnderlines();
746             size_t numUnderlines = underlines.size();
747 
748             for (size_t index = 0; index < numUnderlines; ++index) {
749                 const CompositionUnderline& underline = underlines[index];
750 
751                 if (underline.endOffset <= start())
752                     // underline is completely before this run.  This might be an underline that sits
753                     // before the first run we draw, or underlines that were within runs we skipped
754                     // due to truncation.
755                     continue;
756 
757                 if (underline.startOffset <= end()) {
758                     // underline intersects this run.  Paint it.
759                     paintCompositionUnderline(context, boxOrigin, underline);
760                     if (underline.endOffset > end() + 1)
761                         // underline also runs into the next run. Bail now, no more marker advancement.
762                         break;
763                 } else
764                     // underline is completely after this run, bail.  A later run will paint it.
765                     break;
766             }
767         }
768     }
769 
770     if (shouldRotate)
771         context->concatCTM(rotation(boxRect, Counterclockwise));
772 }
773 
selectionStartEnd(int & sPos,int & ePos)774 void InlineTextBox::selectionStartEnd(int& sPos, int& ePos)
775 {
776     int startPos, endPos;
777     if (renderer()->selectionState() == RenderObject::SelectionInside) {
778         startPos = 0;
779         endPos = textRenderer()->textLength();
780     } else {
781         textRenderer()->selectionStartEnd(startPos, endPos);
782         if (renderer()->selectionState() == RenderObject::SelectionStart)
783             endPos = textRenderer()->textLength();
784         else if (renderer()->selectionState() == RenderObject::SelectionEnd)
785             startPos = 0;
786     }
787 
788     sPos = max(startPos - m_start, 0);
789     ePos = min(endPos - m_start, (int)m_len);
790 }
791 
paintSelection(GraphicsContext * context,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font)792 void InlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font)
793 {
794     // See if we have a selection to paint at all.
795     int sPos, ePos;
796     selectionStartEnd(sPos, ePos);
797     if (sPos >= ePos)
798         return;
799 
800     Color textColor = style->visitedDependentColor(CSSPropertyColor);
801     Color c = renderer()->selectionBackgroundColor();
802     if (!c.isValid() || c.alpha() == 0)
803         return;
804 
805     // If the text color ends up being the same as the selection background, invert the selection
806     // background.  This should basically never happen, since the selection has transparency.
807     if (textColor == c)
808         c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
809 
810     context->save();
811     updateGraphicsContext(context, c, c, 0, style->colorSpace());  // Don't draw text at all!
812 
813     // If the text is truncated, let the thing being painted in the truncation
814     // draw its own highlight.
815     int length = m_truncation != cNoTruncation ? m_truncation : m_len;
816     const UChar* characters = textRenderer()->text()->characters() + m_start;
817 
818     BufferForAppendingHyphen charactersWithHyphen;
819     if (ePos == length && hasHyphen()) {
820         adjustCharactersAndLengthForHyphen(charactersWithHyphen, style, characters, length);
821         ePos = length;
822     }
823 
824     int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
825     int selHeight = selectionHeight();
826     FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
827     context->clip(FloatRect(localOrigin, FloatSize(m_logicalWidth, selHeight)));
828     context->drawHighlightForText(font, TextRun(characters, length, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(),
829                                   !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()),
830                                   localOrigin, selHeight, c, style->colorSpace(), sPos, ePos);
831     context->restore();
832 }
833 
paintCompositionBackground(GraphicsContext * context,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font,int startPos,int endPos)834 void InlineTextBox::paintCompositionBackground(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, int startPos, int endPos)
835 {
836     int offset = m_start;
837     int sPos = max(startPos - offset, 0);
838     int ePos = min(endPos - offset, (int)m_len);
839 
840     if (sPos >= ePos)
841         return;
842 
843     context->save();
844 
845     Color c = Color(225, 221, 85);
846 
847     updateGraphicsContext(context, c, c, 0, style->colorSpace()); // Don't draw text at all!
848 
849     int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
850     int selHeight = selectionHeight();
851     FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
852     context->drawHighlightForText(font, TextRun(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(),
853                                   !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()),
854                                   localOrigin, selHeight, c, style->colorSpace(), sPos, ePos);
855     context->restore();
856 }
857 
858 #if PLATFORM(MAC)
859 
paintCustomHighlight(int tx,int ty,const AtomicString & type)860 void InlineTextBox::paintCustomHighlight(int tx, int ty, const AtomicString& type)
861 {
862     Frame* frame = renderer()->frame();
863     if (!frame)
864         return;
865     Page* page = frame->page();
866     if (!page)
867         return;
868 
869     RootInlineBox* r = root();
870     FloatRect rootRect(tx + r->x(), ty + selectionTop(), r->logicalWidth(), selectionHeight());
871     FloatRect textRect(tx + x(), rootRect.y(), logicalWidth(), rootRect.height());
872 
873     page->chrome()->client()->paintCustomHighlight(renderer()->node(), type, textRect, rootRect, true, false);
874 }
875 
876 #endif
877 
paintDecoration(GraphicsContext * context,const FloatPoint & boxOrigin,int deco,const ShadowData * shadow)878 void InlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& boxOrigin, int deco, const ShadowData* shadow)
879 {
880     if (m_truncation == cFullTruncation)
881         return;
882 
883     FloatPoint localOrigin = boxOrigin;
884 
885     float width = m_logicalWidth;
886     if (m_truncation != cNoTruncation) {
887         width = toRenderText(renderer())->width(m_start, m_truncation, textPos(), m_firstLine);
888         if (!isLeftToRightDirection())
889             localOrigin.move(m_logicalWidth - width, 0);
890     }
891 
892     // Get the text decoration colors.
893     Color underline, overline, linethrough;
894     renderer()->getTextDecorationColors(deco, underline, overline, linethrough, true);
895 
896     // Use a special function for underlines to get the positioning exactly right.
897     bool isPrinting = textRenderer()->document()->printing();
898     context->setStrokeThickness(1.0f); // FIXME: We should improve this rule and not always just assume 1.
899 
900     bool linesAreOpaque = !isPrinting && (!(deco & UNDERLINE) || underline.alpha() == 255) && (!(deco & OVERLINE) || overline.alpha() == 255) && (!(deco & LINE_THROUGH) || linethrough.alpha() == 255);
901 
902     RenderStyle* styleToUse = renderer()->style(m_firstLine);
903     int baseline = styleToUse->fontMetrics().ascent();
904 
905     bool setClip = false;
906     int extraOffset = 0;
907     if (!linesAreOpaque && shadow && shadow->next()) {
908         context->save();
909         FloatRect clipRect(localOrigin, FloatSize(width, baseline + 2));
910         for (const ShadowData* s = shadow; s; s = s->next()) {
911             FloatRect shadowRect(localOrigin, FloatSize(width, baseline + 2));
912             shadowRect.inflate(s->blur());
913             int shadowX = isHorizontal() ? s->x() : s->y();
914             int shadowY = isHorizontal() ? s->y() : -s->x();
915             shadowRect.move(shadowX, shadowY);
916             clipRect.unite(shadowRect);
917             extraOffset = max(extraOffset, max(0, shadowY) + s->blur());
918         }
919         context->save();
920         context->clip(clipRect);
921         extraOffset += baseline + 2;
922         localOrigin.move(0, extraOffset);
923         setClip = true;
924     }
925 
926     ColorSpace colorSpace = renderer()->style()->colorSpace();
927     bool setShadow = false;
928 
929     do {
930         if (shadow) {
931             if (!shadow->next()) {
932                 // The last set of lines paints normally inside the clip.
933                 localOrigin.move(0, -extraOffset);
934                 extraOffset = 0;
935             }
936             int shadowX = isHorizontal() ? shadow->x() : shadow->y();
937             int shadowY = isHorizontal() ? shadow->y() : -shadow->x();
938             context->setShadow(FloatSize(shadowX, shadowY - extraOffset), shadow->blur(), shadow->color(), colorSpace);
939             setShadow = true;
940             shadow = shadow->next();
941         }
942 
943         if (deco & UNDERLINE) {
944             context->setStrokeColor(underline, colorSpace);
945             context->setStrokeStyle(SolidStroke);
946             // Leave one pixel of white between the baseline and the underline.
947             context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + baseline + 1), width, isPrinting);
948         }
949         if (deco & OVERLINE) {
950             context->setStrokeColor(overline, colorSpace);
951             context->setStrokeStyle(SolidStroke);
952             context->drawLineForText(localOrigin, width, isPrinting);
953         }
954         if (deco & LINE_THROUGH) {
955             context->setStrokeColor(linethrough, colorSpace);
956             context->setStrokeStyle(SolidStroke);
957             context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + 2 * baseline / 3), width, isPrinting);
958         }
959     } while (shadow);
960 
961     if (setClip)
962         context->restore();
963     else if (setShadow)
964         context->clearShadow();
965 }
966 
textCheckingLineStyleForMarkerType(DocumentMarker::MarkerType markerType)967 static GraphicsContext::TextCheckingLineStyle textCheckingLineStyleForMarkerType(DocumentMarker::MarkerType markerType)
968 {
969     switch (markerType) {
970     case DocumentMarker::Spelling:
971         return GraphicsContext::TextCheckingSpellingLineStyle;
972     case DocumentMarker::Grammar:
973         return GraphicsContext::TextCheckingGrammarLineStyle;
974     case DocumentMarker::CorrectionIndicator:
975         return GraphicsContext::TextCheckingReplacementLineStyle;
976     default:
977         ASSERT_NOT_REACHED();
978         return GraphicsContext::TextCheckingSpellingLineStyle;
979     }
980 }
981 
paintSpellingOrGrammarMarker(GraphicsContext * pt,const FloatPoint & boxOrigin,const DocumentMarker & marker,RenderStyle * style,const Font & font,bool grammar)982 void InlineTextBox::paintSpellingOrGrammarMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, const DocumentMarker& marker, RenderStyle* style, const Font& font, bool grammar)
983 {
984     // Never print spelling/grammar markers (5327887)
985     if (textRenderer()->document()->printing())
986         return;
987 
988     if (m_truncation == cFullTruncation)
989         return;
990 
991     float start = 0; // start of line to draw, relative to tx
992     float width = m_logicalWidth; // how much line to draw
993 
994     // Determine whether we need to measure text
995     bool markerSpansWholeBox = true;
996     if (m_start <= (int)marker.startOffset)
997         markerSpansWholeBox = false;
998     if ((end() + 1) != marker.endOffset)      // end points at the last char, not past it
999         markerSpansWholeBox = false;
1000     if (m_truncation != cNoTruncation)
1001         markerSpansWholeBox = false;
1002 
1003     if (!markerSpansWholeBox || grammar) {
1004         int startPosition = max<int>(marker.startOffset - m_start, 0);
1005         int endPosition = min<int>(marker.endOffset - m_start, m_len);
1006 
1007         if (m_truncation != cNoTruncation)
1008             endPosition = min<int>(endPosition, m_truncation);
1009 
1010         // Calculate start & width
1011         int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1012         int selHeight = selectionHeight();
1013         FloatPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY);
1014         TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered());
1015 
1016         // FIXME: Convert the document markers to float rects.
1017         IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, selHeight, startPosition, endPosition));
1018         start = markerRect.x() - startPoint.x();
1019         width = markerRect.width();
1020 
1021         // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to
1022         // display a toolTip. We don't do this for misspelling markers.
1023         if (grammar) {
1024             markerRect.move(-boxOrigin.x(), -boxOrigin.y());
1025             markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1026             renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect);
1027         }
1028     }
1029 
1030     // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to
1031     // make sure to fit within those bounds.  This means the top pixel(s) of the underline will overlap the
1032     // bottom pixel(s) of the glyphs in smaller font sizes.  The alternatives are to increase the line spacing (bad!!)
1033     // or decrease the underline thickness.  The overlap is actually the most useful, and matches what AppKit does.
1034     // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so
1035     // we pin to two pixels under the baseline.
1036     int lineThickness = cMisspellingLineThickness;
1037     int baseline = renderer()->style(m_firstLine)->fontMetrics().ascent();
1038     int descent = logicalHeight() - baseline;
1039     int underlineOffset;
1040     if (descent <= (2 + lineThickness)) {
1041         // Place the underline at the very bottom of the text in small/medium fonts.
1042         underlineOffset = logicalHeight() - lineThickness;
1043     } else {
1044         // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
1045         underlineOffset = baseline + 2;
1046     }
1047     pt->drawLineForTextChecking(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, textCheckingLineStyleForMarkerType(marker.type));
1048 }
1049 
paintTextMatchMarker(GraphicsContext * pt,const FloatPoint & boxOrigin,const DocumentMarker & marker,RenderStyle * style,const Font & font)1050 void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, const DocumentMarker& marker, RenderStyle* style, const Font& font)
1051 {
1052     // Use same y positioning and height as for selection, so that when the selection and this highlight are on
1053     // the same word there are no pieces sticking out.
1054     int deltaY = renderer()->style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1055     int selHeight = selectionHeight();
1056 
1057     int sPos = max(marker.startOffset - m_start, (unsigned)0);
1058     int ePos = min(marker.endOffset - m_start, (unsigned)m_len);
1059     TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered());
1060 
1061     // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates.
1062     IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, IntPoint(m_x, selectionTop()), selHeight, sPos, ePos));
1063     markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1064     renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect);
1065 
1066     // Optionally highlight the text
1067     if (renderer()->frame()->editor()->markedTextMatchesAreHighlighted()) {
1068         Color color = marker.activeMatch ?
1069             renderer()->theme()->platformActiveTextSearchHighlightColor() :
1070             renderer()->theme()->platformInactiveTextSearchHighlightColor();
1071         pt->save();
1072         updateGraphicsContext(pt, color, color, 0, style->colorSpace());  // Don't draw text at all!
1073 #if PLATFORM(ANDROID)
1074         pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, style->colorSpace(), sPos, ePos, marker.activeMatch);
1075 #else
1076         pt->clip(FloatRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selHeight));
1077         pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, style->colorSpace(), sPos, ePos);
1078 #endif
1079         pt->restore();
1080     }
1081 }
1082 
computeRectForReplacementMarker(const DocumentMarker & marker,RenderStyle * style,const Font & font)1083 void InlineTextBox::computeRectForReplacementMarker(const DocumentMarker& marker, RenderStyle* style, const Font& font)
1084 {
1085     // Replacement markers are not actually drawn, but their rects need to be computed for hit testing.
1086     int y = selectionTop();
1087     int h = selectionHeight();
1088 
1089     int sPos = max(marker.startOffset - m_start, (unsigned)0);
1090     int ePos = min(marker.endOffset - m_start, (unsigned)m_len);
1091     TextRun run(textRenderer()->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered());
1092     IntPoint startPoint = IntPoint(m_x, y);
1093 
1094     // Compute and store the rect associated with this marker.
1095     IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, h, sPos, ePos));
1096     markerRect = renderer()->localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1097     renderer()->document()->markers()->setRenderedRectForMarker(renderer()->node(), marker, markerRect);
1098 }
1099 
paintDocumentMarkers(GraphicsContext * pt,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font,bool background)1100 void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, bool background)
1101 {
1102     if (!renderer()->node())
1103         return;
1104 
1105     Vector<DocumentMarker> markers = renderer()->document()->markers()->markersForNode(renderer()->node());
1106     Vector<DocumentMarker>::iterator markerIt = markers.begin();
1107 
1108     // Give any document markers that touch this run a chance to draw before the text has been drawn.
1109     // Note end() points at the last char, not one past it like endOffset and ranges do.
1110     for ( ; markerIt != markers.end(); markerIt++) {
1111         const DocumentMarker& marker = *markerIt;
1112 
1113         // Paint either the background markers or the foreground markers, but not both
1114         switch (marker.type) {
1115             case DocumentMarker::Grammar:
1116             case DocumentMarker::Spelling:
1117             case DocumentMarker::CorrectionIndicator:
1118             case DocumentMarker::Replacement:
1119                 if (background)
1120                     continue;
1121                 break;
1122             case DocumentMarker::TextMatch:
1123                 if (!background)
1124                     continue;
1125                 break;
1126             default:
1127                 continue;
1128         }
1129 
1130         if (marker.endOffset <= start())
1131             // marker is completely before this run.  This might be a marker that sits before the
1132             // first run we draw, or markers that were within runs we skipped due to truncation.
1133             continue;
1134 
1135         if (marker.startOffset > end())
1136             // marker is completely after this run, bail.  A later run will paint it.
1137             break;
1138 
1139         // marker intersects this run.  Paint it.
1140         switch (marker.type) {
1141             case DocumentMarker::Spelling:
1142                 paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, false);
1143                 break;
1144             case DocumentMarker::Grammar:
1145                 paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, true);
1146                 break;
1147             case DocumentMarker::TextMatch:
1148                 paintTextMatchMarker(pt, boxOrigin, marker, style, font);
1149                 break;
1150             case DocumentMarker::CorrectionIndicator:
1151                 paintSpellingOrGrammarMarker(pt, boxOrigin, marker, style, font, false);
1152                 break;
1153             case DocumentMarker::Replacement:
1154                 computeRectForReplacementMarker(marker, style, font);
1155                 break;
1156             default:
1157                 ASSERT_NOT_REACHED();
1158         }
1159 
1160     }
1161 }
1162 
paintCompositionUnderline(GraphicsContext * ctx,const FloatPoint & boxOrigin,const CompositionUnderline & underline)1163 void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const FloatPoint& boxOrigin, const CompositionUnderline& underline)
1164 {
1165     if (m_truncation == cFullTruncation)
1166         return;
1167 
1168     float start = 0; // start of line to draw, relative to tx
1169     float width = m_logicalWidth; // how much line to draw
1170     bool useWholeWidth = true;
1171     unsigned paintStart = m_start;
1172     unsigned paintEnd = end() + 1; // end points at the last char, not past it
1173     if (paintStart <= underline.startOffset) {
1174         paintStart = underline.startOffset;
1175         useWholeWidth = false;
1176         start = toRenderText(renderer())->width(m_start, paintStart - m_start, textPos(), m_firstLine);
1177     }
1178     if (paintEnd != underline.endOffset) {      // end points at the last char, not past it
1179         paintEnd = min(paintEnd, (unsigned)underline.endOffset);
1180         useWholeWidth = false;
1181     }
1182     if (m_truncation != cNoTruncation) {
1183         paintEnd = min(paintEnd, (unsigned)m_start + m_truncation);
1184         useWholeWidth = false;
1185     }
1186     if (!useWholeWidth) {
1187         width = toRenderText(renderer())->width(paintStart, paintEnd - paintStart, textPos() + start, m_firstLine);
1188     }
1189 
1190     // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
1191     // All other marked text underlines are 1px thick.
1192     // If there's not enough space the underline will touch or overlap characters.
1193     int lineThickness = 1;
1194     int baseline = renderer()->style(m_firstLine)->fontMetrics().ascent();
1195     if (underline.thick && logicalHeight() - baseline >= 2)
1196         lineThickness = 2;
1197 
1198     // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
1199     // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
1200     start += 1;
1201     width -= 2;
1202 
1203     ctx->setStrokeColor(underline.color, renderer()->style()->colorSpace());
1204     ctx->setStrokeThickness(lineThickness);
1205     ctx->drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, textRenderer()->document()->printing());
1206 }
1207 
caretMinOffset() const1208 int InlineTextBox::caretMinOffset() const
1209 {
1210     return m_start;
1211 }
1212 
caretMaxOffset() const1213 int InlineTextBox::caretMaxOffset() const
1214 {
1215     return m_start + m_len;
1216 }
1217 
caretMaxRenderedOffset() const1218 unsigned InlineTextBox::caretMaxRenderedOffset() const
1219 {
1220     return m_start + m_len;
1221 }
1222 
textPos() const1223 float InlineTextBox::textPos() const
1224 {
1225     // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset
1226     // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width.
1227     if (logicalLeft() == 0)
1228         return 0;
1229     return logicalLeft() - root()->logicalLeft();
1230 }
1231 
offsetForPosition(float lineOffset,bool includePartialGlyphs) const1232 int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs) const
1233 {
1234     if (isLineBreak())
1235         return 0;
1236 
1237     int leftOffset = isLeftToRightDirection() ? 0 : m_len;
1238     int rightOffset = isLeftToRightDirection() ? m_len : 0;
1239     bool blockIsInOppositeDirection = renderer()->containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection();
1240     if (blockIsInOppositeDirection)
1241         swap(leftOffset, rightOffset);
1242 
1243     if (lineOffset - logicalLeft() > logicalWidth())
1244         return rightOffset;
1245     if (lineOffset - logicalLeft() < 0)
1246         return leftOffset;
1247 
1248     RenderText* text = toRenderText(renderer());
1249     RenderStyle* style = text->style(m_firstLine);
1250     const Font* f = &style->font();
1251     int offset = f->offsetForPosition(TextRun(textRenderer()->text()->characters() + m_start, m_len,
1252         textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride || style->visuallyOrdered()),
1253         lineOffset - logicalLeft(), includePartialGlyphs);
1254     if (blockIsInOppositeDirection && (!offset || offset == m_len))
1255         return !offset ? m_len : 0;
1256     return offset;
1257 }
1258 
positionForOffset(int offset) const1259 float InlineTextBox::positionForOffset(int offset) const
1260 {
1261     ASSERT(offset >= m_start);
1262     ASSERT(offset <= m_start + m_len);
1263 
1264     if (isLineBreak())
1265         return logicalLeft();
1266 
1267     RenderText* text = toRenderText(renderer());
1268     const Font& f = text->style(m_firstLine)->font();
1269     int from = !isLeftToRightDirection() ? offset - m_start : 0;
1270     int to = !isLeftToRightDirection() ? m_len : offset - m_start;
1271     // FIXME: Do we need to add rightBearing here?
1272     return f.selectionRectForText(TextRun(text->text()->characters() + m_start, m_len, textRenderer()->allowTabs(), textPos(), m_expansion, expansionBehavior(), !isLeftToRightDirection(), m_dirOverride),
1273                                   IntPoint(logicalLeft(), 0), 0, from, to).maxX();
1274 }
1275 
containsCaretOffset(int offset) const1276 bool InlineTextBox::containsCaretOffset(int offset) const
1277 {
1278     // Offsets before the box are never "in".
1279     if (offset < m_start)
1280         return false;
1281 
1282     int pastEnd = m_start + m_len;
1283 
1284     // Offsets inside the box (not at either edge) are always "in".
1285     if (offset < pastEnd)
1286         return true;
1287 
1288     // Offsets outside the box are always "out".
1289     if (offset > pastEnd)
1290         return false;
1291 
1292     // Offsets at the end are "out" for line breaks (they are on the next line).
1293     if (isLineBreak())
1294         return false;
1295 
1296     // Offsets at the end are "in" for normal boxes (but the caller has to check affinity).
1297     return true;
1298 }
1299 
1300 } // namespace WebCore
1301