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