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