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 "core/rendering/InlineTextBox.h"
25
26 #include "core/dom/Document.h"
27 #include "core/dom/DocumentMarkerController.h"
28 #include "core/dom/RenderedDocumentMarker.h"
29 #include "core/dom/Text.h"
30 #include "core/editing/CompositionUnderline.h"
31 #include "core/editing/CompositionUnderlineRangeFilter.h"
32 #include "core/editing/Editor.h"
33 #include "core/editing/InputMethodController.h"
34 #include "core/frame/LocalFrame.h"
35 #include "core/page/Page.h"
36 #include "core/frame/Settings.h"
37 #include "core/rendering/AbstractInlineTextBox.h"
38 #include "core/rendering/EllipsisBox.h"
39 #include "core/rendering/HitTestResult.h"
40 #include "core/rendering/PaintInfo.h"
41 #include "core/rendering/RenderBR.h"
42 #include "core/rendering/RenderBlock.h"
43 #include "core/rendering/RenderCombineText.h"
44 #include "core/rendering/RenderRubyRun.h"
45 #include "core/rendering/RenderRubyText.h"
46 #include "core/rendering/RenderTheme.h"
47 #include "core/rendering/style/ShadowList.h"
48 #include "core/rendering/svg/SVGTextRunRenderingContext.h"
49 #include "platform/fonts/FontCache.h"
50 #include "platform/fonts/GlyphBuffer.h"
51 #include "platform/fonts/WidthIterator.h"
52 #include "platform/graphics/DrawLooperBuilder.h"
53 #include "platform/graphics/GraphicsContextStateSaver.h"
54 #include "wtf/Vector.h"
55 #include "wtf/text/CString.h"
56 #include "wtf/text/StringBuilder.h"
57
58 #include <algorithm>
59
60 using namespace std;
61
62 namespace WebCore {
63
64 struct SameSizeAsInlineTextBox : public InlineBox {
65 unsigned variables[1];
66 unsigned short variables2[2];
67 void* pointers[2];
68 };
69
70 COMPILE_ASSERT(sizeof(InlineTextBox) == sizeof(SameSizeAsInlineTextBox), InlineTextBox_should_stay_small);
71
72 typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap;
73 static InlineTextBoxOverflowMap* gTextBoxesWithOverflow;
74
75 static const int misspellingLineThickness = 3;
76
destroy()77 void InlineTextBox::destroy()
78 {
79 AbstractInlineTextBox::willDestroy(this);
80
81 if (!knownToHaveNoOverflow() && gTextBoxesWithOverflow)
82 gTextBoxesWithOverflow->remove(this);
83 InlineBox::destroy();
84 }
85
markDirty()86 void InlineTextBox::markDirty()
87 {
88 m_len = 0;
89 m_start = 0;
90 InlineBox::markDirty();
91 }
92
logicalOverflowRect() const93 LayoutRect InlineTextBox::logicalOverflowRect() const
94 {
95 if (knownToHaveNoOverflow() || !gTextBoxesWithOverflow)
96 return enclosingIntRect(logicalFrameRect());
97 return gTextBoxesWithOverflow->get(this);
98 }
99
setLogicalOverflowRect(const LayoutRect & rect)100 void InlineTextBox::setLogicalOverflowRect(const LayoutRect& rect)
101 {
102 ASSERT(!knownToHaveNoOverflow());
103 if (!gTextBoxesWithOverflow)
104 gTextBoxesWithOverflow = new InlineTextBoxOverflowMap;
105 gTextBoxesWithOverflow->add(this, rect);
106 }
107
baselinePosition(FontBaseline baselineType) const108 int InlineTextBox::baselinePosition(FontBaseline baselineType) const
109 {
110 if (!isText() || !parent())
111 return 0;
112 if (parent()->renderer() == renderer().parent())
113 return parent()->baselinePosition(baselineType);
114 return toRenderBoxModelObject(renderer().parent())->baselinePosition(baselineType, isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
115 }
116
lineHeight() const117 LayoutUnit InlineTextBox::lineHeight() const
118 {
119 if (!isText() || !renderer().parent())
120 return 0;
121 if (renderer().isBR())
122 return toRenderBR(renderer()).lineHeight(isFirstLineStyle());
123 if (parent()->renderer() == renderer().parent())
124 return parent()->lineHeight();
125 return toRenderBoxModelObject(renderer().parent())->lineHeight(isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine);
126 }
127
selectionTop()128 LayoutUnit InlineTextBox::selectionTop()
129 {
130 return root().selectionTop();
131 }
132
selectionBottom()133 LayoutUnit InlineTextBox::selectionBottom()
134 {
135 return root().selectionBottom();
136 }
137
selectionHeight()138 LayoutUnit InlineTextBox::selectionHeight()
139 {
140 return root().selectionHeight();
141 }
142
isSelected(int startPos,int endPos) const143 bool InlineTextBox::isSelected(int startPos, int endPos) const
144 {
145 int sPos = max(startPos - m_start, 0);
146 // The position after a hard line break is considered to be past its end.
147 // See the corresponding code in InlineTextBox::selectionState.
148 int ePos = min(endPos - m_start, int(m_len) + (isLineBreak() ? 0 : 1));
149 return (sPos < ePos);
150 }
151
selectionState()152 RenderObject::SelectionState InlineTextBox::selectionState()
153 {
154 RenderObject::SelectionState state = renderer().selectionState();
155 if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) {
156 int startPos, endPos;
157 renderer().selectionStartEnd(startPos, endPos);
158 // The position after a hard line break is considered to be past its end.
159 // See the corresponding code in InlineTextBox::isSelected.
160 int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0);
161
162 // FIXME: Remove -webkit-line-break: LineBreakAfterWhiteSpace.
163 int endOfLineAdjustmentForCSSLineBreak = renderer().style()->lineBreak() == LineBreakAfterWhiteSpace ? -1 : 0;
164 bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos <= m_start + m_len + endOfLineAdjustmentForCSSLineBreak);
165 bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable);
166 if (start && end)
167 state = RenderObject::SelectionBoth;
168 else if (start)
169 state = RenderObject::SelectionStart;
170 else if (end)
171 state = RenderObject::SelectionEnd;
172 else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
173 (state == RenderObject::SelectionStart || endPos > lastSelectable))
174 state = RenderObject::SelectionInside;
175 else if (state == RenderObject::SelectionBoth)
176 state = RenderObject::SelectionNone;
177 }
178
179 // If there are ellipsis following, make sure their selection is updated.
180 if (m_truncation != cNoTruncation && root().ellipsisBox()) {
181 EllipsisBox* ellipsis = root().ellipsisBox();
182 if (state != RenderObject::SelectionNone) {
183 int start, end;
184 selectionStartEnd(start, end);
185 // The ellipsis should be considered to be selected if the end of
186 // the selection is past the beginning of the truncation and the
187 // beginning of the selection is before or at the beginning of the
188 // truncation.
189 ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ?
190 RenderObject::SelectionInside : RenderObject::SelectionNone);
191 } else
192 ellipsis->setSelectionState(RenderObject::SelectionNone);
193 }
194
195 return state;
196 }
197
localSelectionRect(int startPos,int endPos)198 LayoutRect InlineTextBox::localSelectionRect(int startPos, int endPos)
199 {
200 int sPos = max(startPos - m_start, 0);
201 int ePos = min(endPos - m_start, (int)m_len);
202
203 if (sPos > ePos)
204 return LayoutRect();
205
206 FontCachePurgePreventer fontCachePurgePreventer;
207
208 LayoutUnit selTop = selectionTop();
209 LayoutUnit selHeight = selectionHeight();
210 RenderStyle* styleToUse = textRenderer().style(isFirstLineStyle());
211 const Font& font = styleToUse->font();
212
213 StringBuilder charactersWithHyphen;
214 bool respectHyphen = ePos == m_len && hasHyphen();
215 TextRun textRun = constructTextRun(styleToUse, font, respectHyphen ? &charactersWithHyphen : 0);
216
217 FloatPoint startingPoint = FloatPoint(logicalLeft(), selTop.toFloat());
218 LayoutRect r;
219 if (sPos || ePos != static_cast<int>(m_len))
220 r = enclosingIntRect(font.selectionRectForText(textRun, startingPoint, selHeight, sPos, ePos));
221 else // Avoid computing the font width when the entire line box is selected as an optimization.
222 r = enclosingIntRect(FloatRect(startingPoint, FloatSize(m_logicalWidth, selHeight.toFloat())));
223
224 LayoutUnit logicalWidth = r.width();
225 if (r.x() > logicalRight())
226 logicalWidth = 0;
227 else if (r.maxX() > logicalRight())
228 logicalWidth = logicalRight() - r.x();
229
230 LayoutPoint topPoint = isHorizontal() ? LayoutPoint(r.x(), selTop) : LayoutPoint(selTop, r.x());
231 LayoutUnit width = isHorizontal() ? logicalWidth : selHeight;
232 LayoutUnit height = isHorizontal() ? selHeight : logicalWidth;
233
234 return LayoutRect(topPoint, LayoutSize(width, height));
235 }
236
deleteLine()237 void InlineTextBox::deleteLine()
238 {
239 toRenderText(renderer()).removeTextBox(this);
240 destroy();
241 }
242
extractLine()243 void InlineTextBox::extractLine()
244 {
245 if (extracted())
246 return;
247
248 toRenderText(renderer()).extractTextBox(this);
249 }
250
attachLine()251 void InlineTextBox::attachLine()
252 {
253 if (!extracted())
254 return;
255
256 toRenderText(renderer()).attachTextBox(this);
257 }
258
placeEllipsisBox(bool flowIsLTR,float visibleLeftEdge,float visibleRightEdge,float ellipsisWidth,float & truncatedWidth,bool & foundBox)259 float InlineTextBox::placeEllipsisBox(bool flowIsLTR, float visibleLeftEdge, float visibleRightEdge, float ellipsisWidth, float &truncatedWidth, bool& foundBox)
260 {
261 if (foundBox) {
262 m_truncation = cFullTruncation;
263 return -1;
264 }
265
266 // For LTR this is the left edge of the box, for RTL, the right edge in parent coordinates.
267 float ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth;
268
269 // Criteria for full truncation:
270 // LTR: the left edge of the ellipsis is to the left of our text run.
271 // RTL: the right edge of the ellipsis is to the right of our text run.
272 bool ltrFullTruncation = flowIsLTR && ellipsisX <= logicalLeft();
273 bool rtlFullTruncation = !flowIsLTR && ellipsisX >= logicalLeft() + logicalWidth();
274 if (ltrFullTruncation || rtlFullTruncation) {
275 // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
276 m_truncation = cFullTruncation;
277 foundBox = true;
278 return -1;
279 }
280
281 bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < logicalRight());
282 bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > logicalLeft());
283 if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) {
284 foundBox = true;
285
286 // The inline box may have different directionality than it's parent. Since truncation
287 // behavior depends both on both the parent and the inline block's directionality, we
288 // must keep track of these separately.
289 bool ltr = isLeftToRightDirection();
290 if (ltr != flowIsLTR) {
291 // Width in pixels of the visible portion of the box, excluding the ellipsis.
292 int visibleBoxWidth = visibleRightEdge - visibleLeftEdge - ellipsisWidth;
293 ellipsisX = ltr ? logicalLeft() + visibleBoxWidth : logicalRight() - visibleBoxWidth;
294 }
295
296 int offset = offsetForPosition(ellipsisX, false);
297 if (offset == 0) {
298 // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start
299 // and the ellipsis edge.
300 m_truncation = cFullTruncation;
301 truncatedWidth += ellipsisWidth;
302 return min(ellipsisX, logicalLeft());
303 }
304
305 // Set the truncation index on the text run.
306 m_truncation = offset;
307
308 // If we got here that means that we were only partially truncated and we need to return the pixel offset at which
309 // to place the ellipsis.
310 float widthOfVisibleText = toRenderText(renderer()).width(m_start, offset, textPos(), flowIsLTR ? LTR : RTL, isFirstLineStyle());
311
312 // The ellipsis needs to be placed just after the last visible character.
313 // Where "after" is defined by the flow directionality, not the inline
314 // box directionality.
315 // e.g. In the case of an LTR inline box truncated in an RTL flow then we can
316 // have a situation such as |Hello| -> |...He|
317 truncatedWidth += widthOfVisibleText + ellipsisWidth;
318 if (flowIsLTR)
319 return logicalLeft() + widthOfVisibleText;
320 else
321 return logicalRight() - widthOfVisibleText - ellipsisWidth;
322 }
323 truncatedWidth += logicalWidth();
324 return -1;
325 }
326
correctedTextColor(Color textColor,Color backgroundColor)327 Color correctedTextColor(Color textColor, Color backgroundColor)
328 {
329 // Adjust the text color if it is too close to the background color,
330 // by darkening or lightening it to move it further away.
331
332 int d = differenceSquared(textColor, backgroundColor);
333 // semi-arbitrarily chose 65025 (255^2) value here after a few tests;
334 if (d > 65025) {
335 return textColor;
336 }
337
338 int distanceFromWhite = differenceSquared(textColor, Color::white);
339 int distanceFromBlack = differenceSquared(textColor, Color::black);
340
341 if (distanceFromWhite < distanceFromBlack) {
342 return textColor.dark();
343 }
344
345 return textColor.light();
346 }
347
updateGraphicsContext(GraphicsContext * context,const Color & fillColor,const Color & strokeColor,float strokeThickness)348 void updateGraphicsContext(GraphicsContext* context, const Color& fillColor, const Color& strokeColor, float strokeThickness)
349 {
350 TextDrawingModeFlags mode = context->textDrawingMode();
351 if (strokeThickness > 0) {
352 TextDrawingModeFlags newMode = mode | TextModeStroke;
353 if (mode != newMode) {
354 context->setTextDrawingMode(newMode);
355 mode = newMode;
356 }
357 }
358
359 if (mode & TextModeFill && fillColor != context->fillColor())
360 context->setFillColor(fillColor);
361
362 if (mode & TextModeStroke) {
363 if (strokeColor != context->strokeColor())
364 context->setStrokeColor(strokeColor);
365 if (strokeThickness != context->strokeThickness())
366 context->setStrokeThickness(strokeThickness);
367 }
368 }
369
isLineBreak() const370 bool InlineTextBox::isLineBreak() const
371 {
372 return renderer().isBR() || (renderer().style()->preserveNewline() && len() == 1 && (*textRenderer().text().impl())[start()] == '\n');
373 }
374
nodeAtPoint(const HitTestRequest & request,HitTestResult & result,const HitTestLocation & locationInContainer,const LayoutPoint & accumulatedOffset,LayoutUnit,LayoutUnit)375 bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/)
376 {
377 if (isLineBreak())
378 return false;
379
380 FloatPoint boxOrigin = locationIncludingFlipping();
381 boxOrigin.moveBy(accumulatedOffset);
382 FloatRect rect(boxOrigin, size());
383 if (m_truncation != cFullTruncation && visibleToHitTestRequest(request) && locationInContainer.intersects(rect)) {
384 renderer().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset)));
385 if (!result.addNodeToRectBasedTestResult(renderer().node(), request, locationInContainer, rect))
386 return true;
387 }
388 return false;
389 }
390
paintTextWithShadows(GraphicsContext * context,const RenderObject & renderer,const Font & font,const TextRun & textRun,const AtomicString & emphasisMark,int emphasisMarkOffset,int startOffset,int endOffset,int truncationPoint,const FloatPoint & textOrigin,const FloatRect & boxRect,const ShadowList * shadowList,bool stroked,bool horizontal)391 static void paintTextWithShadows(GraphicsContext* context,
392 const RenderObject& renderer, const Font& font, const TextRun& textRun,
393 const AtomicString& emphasisMark, int emphasisMarkOffset,
394 int startOffset, int endOffset, int truncationPoint,
395 const FloatPoint& textOrigin, const FloatRect& boxRect,
396 const ShadowList* shadowList, bool stroked, bool horizontal)
397 {
398 // Text shadows are disabled when printing. http://crbug.com/258321
399 bool hasShadow = shadowList && !context->printing();
400
401 if (hasShadow) {
402 OwnPtr<DrawLooperBuilder> drawLooperBuilder = DrawLooperBuilder::create();
403 for (size_t i = shadowList->shadows().size(); i--; ) {
404 const ShadowData& shadow = shadowList->shadows()[i];
405 float shadowX = horizontal ? shadow.x() : shadow.y();
406 float shadowY = horizontal ? shadow.y() : -shadow.x();
407 FloatSize offset(shadowX, shadowY);
408 drawLooperBuilder->addShadow(offset, shadow.blur(), shadow.color(),
409 DrawLooperBuilder::ShadowRespectsTransforms, DrawLooperBuilder::ShadowIgnoresAlpha);
410 }
411 drawLooperBuilder->addUnmodifiedContent();
412 context->setDrawLooper(drawLooperBuilder.release());
413 }
414
415 TextRunPaintInfo textRunPaintInfo(textRun);
416 textRunPaintInfo.bounds = boxRect;
417 if (startOffset <= endOffset) {
418 textRunPaintInfo.from = startOffset;
419 textRunPaintInfo.to = endOffset;
420 if (emphasisMark.isEmpty())
421 context->drawText(font, textRunPaintInfo, textOrigin);
422 else
423 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset));
424 } else {
425 if (endOffset > 0) {
426 textRunPaintInfo.from = 0;
427 textRunPaintInfo.to = endOffset;
428 if (emphasisMark.isEmpty())
429 context->drawText(font, textRunPaintInfo, textOrigin);
430 else
431 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset));
432 }
433 if (startOffset < truncationPoint) {
434 textRunPaintInfo.from = startOffset;
435 textRunPaintInfo.to = truncationPoint;
436 if (emphasisMark.isEmpty())
437 context->drawText(font, textRunPaintInfo, textOrigin);
438 else
439 context->drawEmphasisMarks(font, textRunPaintInfo, emphasisMark, textOrigin + IntSize(0, emphasisMarkOffset));
440 }
441 }
442
443 if (hasShadow)
444 context->clearDrawLooper();
445 }
446
getEmphasisMarkPosition(RenderStyle * style,TextEmphasisPosition & emphasisPosition) const447 bool InlineTextBox::getEmphasisMarkPosition(RenderStyle* style, TextEmphasisPosition& emphasisPosition) const
448 {
449 // This function returns true if there are text emphasis marks and they are suppressed by ruby text.
450 if (style->textEmphasisMark() == TextEmphasisMarkNone)
451 return false;
452
453 emphasisPosition = style->textEmphasisPosition();
454 if (emphasisPosition == TextEmphasisPositionUnder)
455 return true; // Ruby text is always over, so it cannot suppress emphasis marks under.
456
457 RenderBlock* containingBlock = renderer().containingBlock();
458 if (!containingBlock->isRubyBase())
459 return true; // This text is not inside a ruby base, so it does not have ruby text over it.
460
461 if (!containingBlock->parent()->isRubyRun())
462 return true; // Cannot get the ruby text.
463
464 RenderRubyText* rubyText = toRenderRubyRun(containingBlock->parent())->rubyText();
465
466 // The emphasis marks over are suppressed only if there is a ruby text box and it not empty.
467 return !rubyText || !rubyText->firstLineBox();
468 }
469
paint(PaintInfo & paintInfo,const LayoutPoint & paintOffset,LayoutUnit,LayoutUnit)470 void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/)
471 {
472 if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(&renderer()) || renderer().style()->visibility() != VISIBLE
473 || m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len)
474 return;
475
476 ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
477
478 LayoutUnit logicalLeftSide = logicalLeftVisualOverflow();
479 LayoutUnit logicalRightSide = logicalRightVisualOverflow();
480 LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y());
481 LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide;
482
483 LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
484 LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
485
486 // When subpixel font scaling is enabled text runs are positioned at
487 // subpixel boundaries on the x-axis and thus there is no reason to
488 // snap the x value. We still round the y-axis to ensure consistent
489 // line heights.
490 LayoutPoint adjustedPaintOffset = RuntimeEnabledFeatures::subpixelFontScalingEnabled()
491 ? LayoutPoint(paintOffset.x(), paintOffset.y().round())
492 : roundedIntPoint(paintOffset);
493
494 if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
495 return;
496
497 bool isPrinting = textRenderer().document().printing();
498
499 // Determine whether or not we're selected.
500 bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone;
501 if (!haveSelection && paintInfo.phase == PaintPhaseSelection)
502 // When only painting the selection, don't bother to paint if there is none.
503 return;
504
505 if (m_truncation != cNoTruncation) {
506 if (renderer().containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) {
507 // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
508 // at which we start drawing text.
509 // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
510 // |Hello|CBA| -> |...He|CBA|
511 // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
512 // farther to the right.
513 // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
514 // truncated string i.e. |Hello|CBA| -> |...lo|CBA|
515 LayoutUnit widthOfVisibleText = toRenderText(renderer()).width(m_start, m_truncation, textPos(), isLeftToRightDirection() ? LTR : RTL, isFirstLineStyle());
516 LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText;
517 // FIXME: The hit testing logic also needs to take this translation into account.
518 LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0);
519 adjustedPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
520 }
521 }
522
523 GraphicsContext* context = paintInfo.context;
524
525 RenderObject& rendererToUse = renderer();
526 RenderStyle* styleToUse = rendererToUse.style(isFirstLineStyle());
527
528 adjustedPaintOffset.move(0, styleToUse->isHorizontalWritingMode() ? 0 : -logicalHeight());
529
530 FloatPoint boxOrigin = locationIncludingFlipping();
531 boxOrigin.move(adjustedPaintOffset.x().toFloat(), adjustedPaintOffset.y().toFloat());
532 FloatRect boxRect(boxOrigin, LayoutSize(logicalWidth(), logicalHeight()));
533
534 RenderCombineText* combinedText = styleToUse->hasTextCombine() && textRenderer().isCombineText() && toRenderCombineText(textRenderer()).isCombined() ? &toRenderCombineText(textRenderer()) : 0;
535
536 bool shouldRotate = !isHorizontal() && !combinedText;
537 if (shouldRotate)
538 context->concatCTM(rotation(boxRect, Clockwise));
539
540 // Determine whether or not we have composition underlines to draw.
541 bool containsComposition = renderer().node() && renderer().frame()->inputMethodController().compositionNode() == renderer().node();
542 bool useCustomUnderlines = containsComposition && renderer().frame()->inputMethodController().compositionUsesCustomUnderlines();
543
544 // Determine the text colors and selection colors.
545 Color textFillColor;
546 Color textStrokeColor;
547 Color emphasisMarkColor;
548 float textStrokeWidth = styleToUse->textStrokeWidth();
549
550 // Text shadows are disabled when printing. http://crbug.com/258321
551 const ShadowList* textShadow = (context->printing() || paintInfo.forceBlackText()) ? 0 : styleToUse->textShadow();
552
553 if (paintInfo.forceBlackText()) {
554 textFillColor = Color::black;
555 textStrokeColor = Color::black;
556 emphasisMarkColor = Color::black;
557 } else {
558 textFillColor = rendererToUse.resolveColor(styleToUse, CSSPropertyWebkitTextFillColor);
559
560 bool forceBackgroundToWhite = false;
561 if (isPrinting) {
562 if (styleToUse->printColorAdjust() == PrintColorAdjustEconomy)
563 forceBackgroundToWhite = true;
564 if (textRenderer().document().settings() && textRenderer().document().settings()->shouldPrintBackgrounds())
565 forceBackgroundToWhite = false;
566 }
567
568 // Make the text fill color legible against a white background
569 if (forceBackgroundToWhite)
570 textFillColor = correctedTextColor(textFillColor, Color::white);
571
572 textStrokeColor = rendererToUse.resolveColor(styleToUse, CSSPropertyWebkitTextStrokeColor);
573
574 // Make the text stroke color legible against a white background
575 if (forceBackgroundToWhite)
576 textStrokeColor = correctedTextColor(textStrokeColor, Color::white);
577
578 emphasisMarkColor = rendererToUse.resolveColor(styleToUse, CSSPropertyWebkitTextEmphasisColor);
579
580 // Make the text stroke color legible against a white background
581 if (forceBackgroundToWhite)
582 emphasisMarkColor = correctedTextColor(emphasisMarkColor, Color::white);
583 }
584
585 bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection);
586 bool paintSelectedTextSeparately = false;
587
588 Color selectionFillColor = textFillColor;
589 Color selectionStrokeColor = textStrokeColor;
590 Color selectionEmphasisMarkColor = emphasisMarkColor;
591 float selectionStrokeWidth = textStrokeWidth;
592 const ShadowList* selectionShadow = textShadow;
593 if (haveSelection) {
594 // Check foreground color first.
595 Color foreground = paintInfo.forceBlackText() ? Color::black : renderer().selectionForegroundColor();
596 if (foreground != selectionFillColor) {
597 if (!paintSelectedTextOnly)
598 paintSelectedTextSeparately = true;
599 selectionFillColor = foreground;
600 }
601
602 Color emphasisMarkForeground = paintInfo.forceBlackText() ? Color::black : renderer().selectionEmphasisMarkColor();
603 if (emphasisMarkForeground != selectionEmphasisMarkColor) {
604 if (!paintSelectedTextOnly)
605 paintSelectedTextSeparately = true;
606 selectionEmphasisMarkColor = emphasisMarkForeground;
607 }
608
609 if (RenderStyle* pseudoStyle = renderer().getCachedPseudoStyle(SELECTION)) {
610 // Text shadows are disabled when printing. http://crbug.com/258321
611 const ShadowList* shadow = (context->printing() || paintInfo.forceBlackText()) ? 0 : pseudoStyle->textShadow();
612 if (shadow != selectionShadow) {
613 if (!paintSelectedTextOnly)
614 paintSelectedTextSeparately = true;
615 selectionShadow = shadow;
616 }
617
618 float strokeWidth = pseudoStyle->textStrokeWidth();
619 if (strokeWidth != selectionStrokeWidth) {
620 if (!paintSelectedTextOnly)
621 paintSelectedTextSeparately = true;
622 selectionStrokeWidth = strokeWidth;
623 }
624
625 Color stroke = paintInfo.forceBlackText() ? Color::black : rendererToUse.resolveColor(pseudoStyle, CSSPropertyWebkitTextStrokeColor);
626 if (stroke != selectionStrokeColor) {
627 if (!paintSelectedTextOnly)
628 paintSelectedTextSeparately = true;
629 selectionStrokeColor = stroke;
630 }
631 }
632 }
633
634 // Set our font.
635 const Font& font = styleToUse->font();
636
637 FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());
638
639 if (combinedText)
640 combinedText->adjustTextOrigin(textOrigin, boxRect);
641
642 // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
643 // and composition highlights.
644 if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
645 if (containsComposition) {
646 paintCompositionBackgrounds(context, boxOrigin, styleToUse, font, useCustomUnderlines);
647 }
648
649 paintDocumentMarkers(context, boxOrigin, styleToUse, font, true);
650
651 if (haveSelection && !useCustomUnderlines)
652 paintSelection(context, boxOrigin, styleToUse, font, selectionFillColor);
653 }
654
655 // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
656 int length = m_len;
657 int maximumLength;
658 StringView string;
659 if (!combinedText) {
660 string = textRenderer().text().createView();
661 if (static_cast<unsigned>(length) != string.length() || m_start)
662 string.narrow(m_start, length);
663 maximumLength = textRenderer().textLength() - m_start;
664 } else {
665 combinedText->getStringToRender(m_start, string, length);
666 maximumLength = length;
667 }
668
669 StringBuilder charactersWithHyphen;
670 TextRun textRun = constructTextRun(styleToUse, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0);
671 if (hasHyphen())
672 length = textRun.length();
673
674 int sPos = 0;
675 int ePos = 0;
676 if (paintSelectedTextOnly || paintSelectedTextSeparately)
677 selectionStartEnd(sPos, ePos);
678
679 if (m_truncation != cNoTruncation) {
680 sPos = min<int>(sPos, m_truncation);
681 ePos = min<int>(ePos, m_truncation);
682 length = m_truncation;
683 }
684
685 int emphasisMarkOffset = 0;
686 TextEmphasisPosition emphasisMarkPosition;
687 bool hasTextEmphasis = getEmphasisMarkPosition(styleToUse, emphasisMarkPosition);
688 const AtomicString& emphasisMark = hasTextEmphasis ? styleToUse->textEmphasisMarkString() : nullAtom;
689 if (!emphasisMark.isEmpty())
690 emphasisMarkOffset = emphasisMarkPosition == TextEmphasisPositionOver ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark);
691
692 if (!paintSelectedTextOnly) {
693 // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side
694 // effect, so only when we know we're stroking, do a save/restore.
695 GraphicsContextStateSaver stateSaver(*context, textStrokeWidth > 0);
696
697 updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth);
698 if (!paintSelectedTextSeparately || ePos <= sPos) {
699 // FIXME: Truncate right-to-left text correctly.
700 paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
701 } else {
702 paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
703 }
704
705 if (!emphasisMark.isEmpty()) {
706 updateGraphicsContext(context, emphasisMarkColor, textStrokeColor, textStrokeWidth);
707
708 DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
709 TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
710 FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
711 if (combinedText)
712 context->concatCTM(rotation(boxRect, Clockwise));
713
714 int startOffset = 0;
715 int endOffset = length;
716 int paintRunLength = length;
717 if (combinedText) {
718 startOffset = 0;
719 endOffset = objectReplacementCharacterTextRun.length();
720 paintRunLength = endOffset;
721 } else if (paintSelectedTextSeparately && ePos > sPos) {
722 startOffset = ePos;
723 endOffset = sPos;
724 }
725 // FIXME: Truncate right-to-left text correctly.
726 paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, startOffset, endOffset, paintRunLength, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal());
727
728 if (combinedText)
729 context->concatCTM(rotation(boxRect, Counterclockwise));
730 }
731 }
732
733 if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) {
734 // paint only the text that is selected
735 GraphicsContextStateSaver stateSaver(*context, selectionStrokeWidth > 0);
736
737 updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth);
738 paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
739 if (!emphasisMark.isEmpty()) {
740 updateGraphicsContext(context, selectionEmphasisMarkColor, textStrokeColor, textStrokeWidth);
741
742 DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
743 TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun;
744 FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin;
745 if (combinedText)
746 context->concatCTM(rotation(boxRect, Clockwise));
747
748 int startOffset = combinedText ? 0 : sPos;
749 int endOffset = combinedText ? objectReplacementCharacterTextRun.length() : ePos;
750 int paintRunLength = combinedText ? endOffset : length;
751 paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, startOffset, endOffset, paintRunLength, emphasisMarkTextOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal());
752
753 if (combinedText)
754 context->concatCTM(rotation(boxRect, Counterclockwise));
755 }
756 }
757
758 // Paint decorations
759 TextDecoration textDecorations = styleToUse->textDecorationsInEffect();
760 if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) {
761 updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth);
762 if (combinedText)
763 context->concatCTM(rotation(boxRect, Clockwise));
764 paintDecoration(context, boxOrigin, textDecorations, textShadow);
765 if (combinedText)
766 context->concatCTM(rotation(boxRect, Counterclockwise));
767 }
768
769 if (paintInfo.phase == PaintPhaseForeground) {
770 paintDocumentMarkers(context, boxOrigin, styleToUse, font, false);
771
772 // Paint custom underlines for compositions.
773 if (useCustomUnderlines) {
774 const Vector<CompositionUnderline>& underlines = renderer().frame()->inputMethodController().customCompositionUnderlines();
775 CompositionUnderlineRangeFilter filter(underlines, start(), end());
776 for (CompositionUnderlineRangeFilter::ConstIterator it = filter.begin(); it != filter.end(); ++it) {
777 if (it->color == Color::transparent)
778 continue;
779 paintCompositionUnderline(context, boxOrigin, *it);
780 }
781 }
782 }
783
784 if (shouldRotate)
785 context->concatCTM(rotation(boxRect, Counterclockwise));
786 }
787
selectionStartEnd(int & sPos,int & ePos)788 void InlineTextBox::selectionStartEnd(int& sPos, int& ePos)
789 {
790 int startPos, endPos;
791 if (renderer().selectionState() == RenderObject::SelectionInside) {
792 startPos = 0;
793 endPos = textRenderer().textLength();
794 } else {
795 textRenderer().selectionStartEnd(startPos, endPos);
796 if (renderer().selectionState() == RenderObject::SelectionStart)
797 endPos = textRenderer().textLength();
798 else if (renderer().selectionState() == RenderObject::SelectionEnd)
799 startPos = 0;
800 }
801
802 sPos = max(startPos - m_start, 0);
803 ePos = min(endPos - m_start, (int)m_len);
804 }
805
alignSelectionRectToDevicePixels(FloatRect & rect)806 void alignSelectionRectToDevicePixels(FloatRect& rect)
807 {
808 float maxX = floorf(rect.maxX());
809 rect.setX(floorf(rect.x()));
810 rect.setWidth(roundf(maxX - rect.x()));
811 }
812
paintSelection(GraphicsContext * context,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font,Color textColor)813 void InlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, Color textColor)
814 {
815 if (context->paintingDisabled())
816 return;
817
818 // See if we have a selection to paint at all.
819 int sPos, ePos;
820 selectionStartEnd(sPos, ePos);
821 if (sPos >= ePos)
822 return;
823
824 Color c = renderer().selectionBackgroundColor();
825 if (!c.alpha())
826 return;
827
828 // If the text color ends up being the same as the selection background, invert the selection
829 // background.
830 if (textColor == c)
831 c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
832
833 GraphicsContextStateSaver stateSaver(*context);
834 updateGraphicsContext(context, c, c, 0); // Don't draw text at all!
835
836 // If the text is truncated, let the thing being painted in the truncation
837 // draw its own highlight.
838 int length = m_truncation != cNoTruncation ? m_truncation : m_len;
839 StringView string = textRenderer().text().createView();
840
841 if (string.length() != static_cast<unsigned>(length) || m_start)
842 string.narrow(m_start, length);
843
844 StringBuilder charactersWithHyphen;
845 bool respectHyphen = ePos == length && hasHyphen();
846 TextRun textRun = constructTextRun(style, font, string, textRenderer().textLength() - m_start, respectHyphen ? &charactersWithHyphen : 0);
847 if (respectHyphen)
848 ePos = textRun.length();
849
850 LayoutUnit selectionBottom = root().selectionBottom();
851 LayoutUnit selectionTop = root().selectionTopAdjustedForPrecedingBlock();
852
853 int deltaY = roundToInt(renderer().style()->isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop);
854 int selHeight = max(0, roundToInt(selectionBottom - selectionTop));
855
856 FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
857 FloatRect clipRect(localOrigin, FloatSize(m_logicalWidth, selHeight));
858 alignSelectionRectToDevicePixels(clipRect);
859
860 context->clip(clipRect);
861
862 context->drawHighlightForText(font, textRun, localOrigin, selHeight, c, sPos, ePos);
863 }
864
underlinePaintStart(const CompositionUnderline & underline)865 unsigned InlineTextBox::underlinePaintStart(const CompositionUnderline& underline)
866 {
867 return std::max(static_cast<unsigned>(m_start), underline.startOffset);
868 }
869
underlinePaintEnd(const CompositionUnderline & underline)870 unsigned InlineTextBox::underlinePaintEnd(const CompositionUnderline& underline)
871 {
872 unsigned paintEnd = std::min(end() + 1, underline.endOffset); // end() points at the last char, not past it.
873 if (m_truncation != cNoTruncation)
874 paintEnd = std::min(paintEnd, static_cast<unsigned>(m_start + m_truncation));
875 return paintEnd;
876 }
877
paintSingleCompositionBackgroundRun(GraphicsContext * context,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font,Color backgroundColor,int startPos,int endPos)878 void InlineTextBox::paintSingleCompositionBackgroundRun(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, Color backgroundColor, int startPos, int endPos)
879 {
880 int sPos = std::max(startPos - m_start, 0);
881 int ePos = std::min(endPos - m_start, static_cast<int>(m_len));
882 if (sPos >= ePos)
883 return;
884
885 GraphicsContextStateSaver stateSaver(*context);
886
887 updateGraphicsContext(context, backgroundColor, backgroundColor, 0); // Don't draw text at all!
888
889 int deltaY = renderer().style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
890 int selHeight = selectionHeight();
891 FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY);
892 context->drawHighlightForText(font, constructTextRun(style, font), localOrigin, selHeight, backgroundColor, sPos, ePos);
893 }
894
textDecorationStyleToStrokeStyle(TextDecorationStyle decorationStyle)895 static StrokeStyle textDecorationStyleToStrokeStyle(TextDecorationStyle decorationStyle)
896 {
897 StrokeStyle strokeStyle = SolidStroke;
898 switch (decorationStyle) {
899 case TextDecorationStyleSolid:
900 strokeStyle = SolidStroke;
901 break;
902 case TextDecorationStyleDouble:
903 strokeStyle = DoubleStroke;
904 break;
905 case TextDecorationStyleDotted:
906 strokeStyle = DottedStroke;
907 break;
908 case TextDecorationStyleDashed:
909 strokeStyle = DashedStroke;
910 break;
911 case TextDecorationStyleWavy:
912 strokeStyle = WavyStroke;
913 break;
914 }
915
916 return strokeStyle;
917 }
918
computeUnderlineOffset(const TextUnderlinePosition underlinePosition,const FontMetrics & fontMetrics,const InlineTextBox * inlineTextBox,const float textDecorationThickness)919 static int computeUnderlineOffset(const TextUnderlinePosition underlinePosition, const FontMetrics& fontMetrics, const InlineTextBox* inlineTextBox, const float textDecorationThickness)
920 {
921 // Compute the gap between the font and the underline. Use at least one
922 // pixel gap, if underline is thick then use a bigger gap.
923 int gap = 0;
924
925 // Underline position of zero means draw underline on Baseline Position,
926 // in Blink we need at least 1-pixel gap to adding following check.
927 // Positive underline Position means underline should be drawn above baselin e
928 // and negative value means drawing below baseline, negating the value as in Blink
929 // downward Y-increases.
930
931 if (fontMetrics.underlinePosition())
932 gap = -fontMetrics.underlinePosition();
933 else
934 gap = std::max<int>(1, ceilf(textDecorationThickness / 2.f));
935
936 // FIXME: We support only horizontal text for now.
937 switch (underlinePosition) {
938 case TextUnderlinePositionAuto:
939 return fontMetrics.ascent() + gap; // Position underline near the alphabetic baseline.
940 case TextUnderlinePositionUnder: {
941 // Position underline relative to the under edge of the lowest element's content box.
942 const float offset = inlineTextBox->root().maxLogicalTop() - inlineTextBox->logicalTop();
943 if (offset > 0)
944 return inlineTextBox->logicalHeight() + gap + offset;
945 return inlineTextBox->logicalHeight() + gap;
946 }
947 }
948
949 ASSERT_NOT_REACHED();
950 return fontMetrics.ascent() + gap;
951 }
952
adjustStepToDecorationLength(float & step,float & controlPointDistance,float length)953 static void adjustStepToDecorationLength(float& step, float& controlPointDistance, float length)
954 {
955 ASSERT(step > 0);
956
957 if (length <= 0)
958 return;
959
960 unsigned stepCount = static_cast<unsigned>(length / step);
961
962 // Each Bezier curve starts at the same pixel that the previous one
963 // ended. We need to subtract (stepCount - 1) pixels when calculating the
964 // length covered to account for that.
965 float uncoveredLength = length - (stepCount * step - (stepCount - 1));
966 float adjustment = uncoveredLength / stepCount;
967 step += adjustment;
968 controlPointDistance += adjustment;
969 }
970
971 /*
972 * Draw one cubic Bezier curve and repeat the same pattern long the the decoration's axis.
973 * The start point (p1), controlPoint1, controlPoint2 and end point (p2) of the Bezier curve
974 * form a diamond shape:
975 *
976 * step
977 * |-----------|
978 *
979 * controlPoint1
980 * +
981 *
982 *
983 * . .
984 * . .
985 * . .
986 * (x1, y1) p1 + . + p2 (x2, y2) - <--- Decoration's axis
987 * . . |
988 * . . |
989 * . . | controlPointDistance
990 * |
991 * |
992 * + -
993 * controlPoint2
994 *
995 * |-----------|
996 * step
997 */
strokeWavyTextDecoration(GraphicsContext * context,FloatPoint p1,FloatPoint p2,float strokeThickness)998 static void strokeWavyTextDecoration(GraphicsContext* context, FloatPoint p1, FloatPoint p2, float strokeThickness)
999 {
1000 context->adjustLineToPixelBoundaries(p1, p2, strokeThickness, context->strokeStyle());
1001
1002 Path path;
1003 path.moveTo(p1);
1004
1005 // Distance between decoration's axis and Bezier curve's control points.
1006 // The height of the curve is based on this distance. Use a minimum of 6 pixels distance since
1007 // the actual curve passes approximately at half of that distance, that is 3 pixels.
1008 // The minimum height of the curve is also approximately 3 pixels. Increases the curve's height
1009 // as strockThickness increases to make the curve looks better.
1010 float controlPointDistance = 3 * max<float>(2, strokeThickness);
1011
1012 // Increment used to form the diamond shape between start point (p1), control
1013 // points and end point (p2) along the axis of the decoration. Makes the
1014 // curve wider as strockThickness increases to make the curve looks better.
1015 float step = 2 * max<float>(2, strokeThickness);
1016
1017 bool isVerticalLine = (p1.x() == p2.x());
1018
1019 if (isVerticalLine) {
1020 ASSERT(p1.x() == p2.x());
1021
1022 float xAxis = p1.x();
1023 float y1;
1024 float y2;
1025
1026 if (p1.y() < p2.y()) {
1027 y1 = p1.y();
1028 y2 = p2.y();
1029 } else {
1030 y1 = p2.y();
1031 y2 = p1.y();
1032 }
1033
1034 adjustStepToDecorationLength(step, controlPointDistance, y2 - y1);
1035 FloatPoint controlPoint1(xAxis + controlPointDistance, 0);
1036 FloatPoint controlPoint2(xAxis - controlPointDistance, 0);
1037
1038 for (float y = y1; y + 2 * step <= y2;) {
1039 controlPoint1.setY(y + step);
1040 controlPoint2.setY(y + step);
1041 y += 2 * step;
1042 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y));
1043 }
1044 } else {
1045 ASSERT(p1.y() == p2.y());
1046
1047 float yAxis = p1.y();
1048 float x1;
1049 float x2;
1050
1051 if (p1.x() < p2.x()) {
1052 x1 = p1.x();
1053 x2 = p2.x();
1054 } else {
1055 x1 = p2.x();
1056 x2 = p1.x();
1057 }
1058
1059 adjustStepToDecorationLength(step, controlPointDistance, x2 - x1);
1060 FloatPoint controlPoint1(0, yAxis + controlPointDistance);
1061 FloatPoint controlPoint2(0, yAxis - controlPointDistance);
1062
1063 for (float x = x1; x + 2 * step <= x2;) {
1064 controlPoint1.setX(x + step);
1065 controlPoint2.setX(x + step);
1066 x += 2 * step;
1067 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis));
1068 }
1069 }
1070
1071 context->setShouldAntialias(true);
1072 context->strokePath(path);
1073 }
1074
shouldSetDecorationAntialias(TextDecorationStyle decorationStyle)1075 static bool shouldSetDecorationAntialias(TextDecorationStyle decorationStyle)
1076 {
1077 return decorationStyle == TextDecorationStyleDotted || decorationStyle == TextDecorationStyleDashed;
1078 }
1079
shouldSetDecorationAntialias(TextDecorationStyle underline,TextDecorationStyle overline,TextDecorationStyle linethrough)1080 static bool shouldSetDecorationAntialias(TextDecorationStyle underline, TextDecorationStyle overline, TextDecorationStyle linethrough)
1081 {
1082 return shouldSetDecorationAntialias(underline) || shouldSetDecorationAntialias(overline) || shouldSetDecorationAntialias(linethrough);
1083 }
1084
paintAppliedDecoration(GraphicsContext * context,FloatPoint start,float width,float doubleOffset,int wavyOffsetFactor,RenderObject::AppliedTextDecoration decoration,float thickness,bool antialiasDecoration,bool isPrinting)1085 static void paintAppliedDecoration(GraphicsContext* context, FloatPoint start, float width, float doubleOffset, int wavyOffsetFactor,
1086 RenderObject::AppliedTextDecoration decoration, float thickness, bool antialiasDecoration, bool isPrinting)
1087 {
1088 context->setStrokeStyle(textDecorationStyleToStrokeStyle(decoration.style));
1089 context->setStrokeColor(decoration.color);
1090
1091 switch (decoration.style) {
1092 case TextDecorationStyleWavy:
1093 strokeWavyTextDecoration(context, start + FloatPoint(0, doubleOffset * wavyOffsetFactor), start + FloatPoint(width, doubleOffset * wavyOffsetFactor), thickness);
1094 break;
1095 case TextDecorationStyleDotted:
1096 case TextDecorationStyleDashed:
1097 context->setShouldAntialias(antialiasDecoration);
1098 // Fall through
1099 default:
1100 context->drawLineForText(start, width, isPrinting);
1101
1102 if (decoration.style == TextDecorationStyleDouble)
1103 context->drawLineForText(start + FloatPoint(0, doubleOffset), width, isPrinting);
1104 }
1105 }
1106
paintDecoration(GraphicsContext * context,const FloatPoint & boxOrigin,TextDecoration deco,const ShadowList * shadowList)1107 void InlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& boxOrigin, TextDecoration deco, const ShadowList* shadowList)
1108 {
1109 GraphicsContextStateSaver stateSaver(*context);
1110
1111 if (m_truncation == cFullTruncation)
1112 return;
1113
1114 FloatPoint localOrigin = boxOrigin;
1115
1116 float width = m_logicalWidth;
1117 if (m_truncation != cNoTruncation) {
1118 width = toRenderText(renderer()).width(m_start, m_truncation, textPos(), isLeftToRightDirection() ? LTR : RTL, isFirstLineStyle());
1119 if (!isLeftToRightDirection())
1120 localOrigin.move(m_logicalWidth - width, 0);
1121 }
1122
1123 // Get the text decoration colors.
1124 RenderObject::AppliedTextDecoration underline, overline, linethrough;
1125
1126 renderer().getTextDecorations(deco, underline, overline, linethrough, true);
1127 if (isFirstLineStyle())
1128 renderer().getTextDecorations(deco, underline, overline, linethrough, true, true);
1129
1130 // Use a special function for underlines to get the positioning exactly right.
1131 bool isPrinting = textRenderer().document().printing();
1132
1133 bool linesAreOpaque = !isPrinting && (!(deco & TextDecorationUnderline) || underline.color.alpha() == 255) && (!(deco & TextDecorationOverline) || overline.color.alpha() == 255) && (!(deco & TextDecorationLineThrough) || linethrough.color.alpha() == 255);
1134
1135 RenderStyle* styleToUse = renderer().style(isFirstLineStyle());
1136 int baseline = styleToUse->fontMetrics().ascent();
1137
1138 size_t shadowCount = shadowList ? shadowList->shadows().size() : 0;
1139 // Set the thick of the line to be 10% (or something else ?)of the computed font size and not less than 1px.
1140 // Using computedFontSize should take care of zoom as well.
1141
1142 // Update Underline thickness, in case we have Faulty Font Metrics calculating underline thickness by old method.
1143 float textDecorationThickness = styleToUse->fontMetrics().underlineThickness();
1144 int fontHeightInt = (int)(styleToUse->fontMetrics().floatHeight() + 0.5);
1145 if ((textDecorationThickness == 0.f) || (textDecorationThickness >= (fontHeightInt >> 1)))
1146 textDecorationThickness = std::max(1.f, styleToUse->computedFontSize() / 10.f);
1147
1148 context->setStrokeThickness(textDecorationThickness);
1149
1150 bool antialiasDecoration = shouldSetDecorationAntialias(overline.style, underline.style, linethrough.style)
1151 && RenderBoxModelObject::shouldAntialiasLines(context);
1152
1153 float extraOffset = 0;
1154 if (!linesAreOpaque && shadowCount > 1) {
1155 FloatRect clipRect(localOrigin, FloatSize(width, baseline + 2));
1156 for (size_t i = shadowCount; i--; ) {
1157 const ShadowData& s = shadowList->shadows()[i];
1158 FloatRect shadowRect(localOrigin, FloatSize(width, baseline + 2));
1159 shadowRect.inflate(s.blur());
1160 float shadowX = isHorizontal() ? s.x() : s.y();
1161 float shadowY = isHorizontal() ? s.y() : -s.x();
1162 shadowRect.move(shadowX, shadowY);
1163 clipRect.unite(shadowRect);
1164 extraOffset = max(extraOffset, max(0.0f, shadowY) + s.blur());
1165 }
1166 context->clip(clipRect);
1167 extraOffset += baseline + 2;
1168 localOrigin.move(0, extraOffset);
1169 }
1170
1171 for (size_t i = max(static_cast<size_t>(1), shadowCount); i--; ) {
1172 // Even if we have no shadows, we still want to run the code below this once.
1173 if (i < shadowCount) {
1174 if (!i) {
1175 // The last set of lines paints normally inside the clip.
1176 localOrigin.move(0, -extraOffset);
1177 extraOffset = 0;
1178 }
1179 const ShadowData& shadow = shadowList->shadows()[i];
1180 float shadowX = isHorizontal() ? shadow.x() : shadow.y();
1181 float shadowY = isHorizontal() ? shadow.y() : -shadow.x();
1182 context->setShadow(FloatSize(shadowX, shadowY - extraOffset), shadow.blur(), shadow.color());
1183 }
1184
1185 // Offset between lines - always non-zero, so lines never cross each other.
1186 float doubleOffset = textDecorationThickness + 1.f;
1187
1188 if (deco & TextDecorationUnderline) {
1189 const int underlineOffset = computeUnderlineOffset(styleToUse->textUnderlinePosition(), styleToUse->fontMetrics(), this, textDecorationThickness);
1190 paintAppliedDecoration(context, localOrigin + FloatPoint(0, underlineOffset), width, doubleOffset, 1, underline, textDecorationThickness, antialiasDecoration, isPrinting);
1191 }
1192 if (deco & TextDecorationOverline) {
1193 paintAppliedDecoration(context, localOrigin, width, -doubleOffset, 1, overline, textDecorationThickness, antialiasDecoration, isPrinting);
1194 }
1195 if (deco & TextDecorationLineThrough) {
1196 const float lineThroughOffset = 2 * baseline / 3;
1197 paintAppliedDecoration(context, localOrigin + FloatPoint(0, lineThroughOffset), width, doubleOffset, 0, linethrough, textDecorationThickness, antialiasDecoration, isPrinting);
1198 }
1199 }
1200 }
1201
lineStyleForMarkerType(DocumentMarker::MarkerType markerType)1202 static GraphicsContext::DocumentMarkerLineStyle lineStyleForMarkerType(DocumentMarker::MarkerType markerType)
1203 {
1204 switch (markerType) {
1205 case DocumentMarker::Spelling:
1206 return GraphicsContext::DocumentMarkerSpellingLineStyle;
1207 case DocumentMarker::Grammar:
1208 return GraphicsContext::DocumentMarkerGrammarLineStyle;
1209 default:
1210 ASSERT_NOT_REACHED();
1211 return GraphicsContext::DocumentMarkerSpellingLineStyle;
1212 }
1213 }
1214
paintDocumentMarker(GraphicsContext * pt,const FloatPoint & boxOrigin,DocumentMarker * marker,RenderStyle * style,const Font & font,bool grammar)1215 void InlineTextBox::paintDocumentMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, RenderStyle* style, const Font& font, bool grammar)
1216 {
1217 // Never print spelling/grammar markers (5327887)
1218 if (textRenderer().document().printing())
1219 return;
1220
1221 if (m_truncation == cFullTruncation)
1222 return;
1223
1224 float start = 0; // start of line to draw, relative to tx
1225 float width = m_logicalWidth; // how much line to draw
1226
1227 // Determine whether we need to measure text
1228 bool markerSpansWholeBox = true;
1229 if (m_start <= (int)marker->startOffset())
1230 markerSpansWholeBox = false;
1231 if ((end() + 1) != marker->endOffset()) // end points at the last char, not past it
1232 markerSpansWholeBox = false;
1233 if (m_truncation != cNoTruncation)
1234 markerSpansWholeBox = false;
1235
1236 if (!markerSpansWholeBox || grammar) {
1237 int startPosition = max<int>(marker->startOffset() - m_start, 0);
1238 int endPosition = min<int>(marker->endOffset() - m_start, m_len);
1239
1240 if (m_truncation != cNoTruncation)
1241 endPosition = min<int>(endPosition, m_truncation);
1242
1243 // Calculate start & width
1244 int deltaY = renderer().style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1245 int selHeight = selectionHeight();
1246 FloatPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY);
1247 TextRun run = constructTextRun(style, font);
1248
1249 // FIXME: Convert the document markers to float rects.
1250 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, selHeight, startPosition, endPosition));
1251 start = markerRect.x() - startPoint.x();
1252 width = markerRect.width();
1253
1254 // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to
1255 // display a toolTip. We don't do this for misspelling markers.
1256 if (grammar) {
1257 markerRect.move(-boxOrigin.x(), -boxOrigin.y());
1258 markerRect = renderer().localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1259 toRenderedDocumentMarker(marker)->setRenderedRect(markerRect);
1260 }
1261 }
1262
1263 // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to
1264 // make sure to fit within those bounds. This means the top pixel(s) of the underline will overlap the
1265 // bottom pixel(s) of the glyphs in smaller font sizes. The alternatives are to increase the line spacing (bad!!)
1266 // or decrease the underline thickness. The overlap is actually the most useful, and matches what AppKit does.
1267 // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so
1268 // we pin to two pixels under the baseline.
1269 int lineThickness = misspellingLineThickness;
1270 int baseline = renderer().style(isFirstLineStyle())->fontMetrics().ascent();
1271 int descent = logicalHeight() - baseline;
1272 int underlineOffset;
1273 if (descent <= (2 + lineThickness)) {
1274 // Place the underline at the very bottom of the text in small/medium fonts.
1275 underlineOffset = logicalHeight() - lineThickness;
1276 } else {
1277 // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
1278 underlineOffset = baseline + 2;
1279 }
1280 pt->drawLineForDocumentMarker(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, lineStyleForMarkerType(marker->type()));
1281 }
1282
paintTextMatchMarker(GraphicsContext * pt,const FloatPoint & boxOrigin,DocumentMarker * marker,RenderStyle * style,const Font & font)1283 void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, RenderStyle* style, const Font& font)
1284 {
1285 // Use same y positioning and height as for selection, so that when the selection and this highlight are on
1286 // the same word there are no pieces sticking out.
1287 int deltaY = renderer().style()->isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop();
1288 int selHeight = selectionHeight();
1289
1290 int sPos = max(marker->startOffset() - m_start, (unsigned)0);
1291 int ePos = min(marker->endOffset() - m_start, (unsigned)m_len);
1292 TextRun run = constructTextRun(style, font);
1293
1294 // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates.
1295 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, IntPoint(x(), selectionTop()), selHeight, sPos, ePos));
1296 markerRect = renderer().localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox();
1297 toRenderedDocumentMarker(marker)->setRenderedRect(markerRect);
1298
1299 // Optionally highlight the text
1300 if (renderer().frame()->editor().markedTextMatchesAreHighlighted()) {
1301 Color color = marker->activeMatch() ?
1302 RenderTheme::theme().platformActiveTextSearchHighlightColor() :
1303 RenderTheme::theme().platformInactiveTextSearchHighlightColor();
1304 GraphicsContextStateSaver stateSaver(*pt);
1305 updateGraphicsContext(pt, color, color, 0); // Don't draw text at all!
1306 pt->clip(FloatRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selHeight));
1307 pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, sPos, ePos);
1308 }
1309 }
1310
paintCompositionBackgrounds(GraphicsContext * pt,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font,bool useCustomUnderlines)1311 void InlineTextBox::paintCompositionBackgrounds(GraphicsContext* pt, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, bool useCustomUnderlines)
1312 {
1313 if (useCustomUnderlines) {
1314 // Paint custom background highlights for compositions.
1315 const Vector<CompositionUnderline>& underlines = renderer().frame()->inputMethodController().customCompositionUnderlines();
1316 CompositionUnderlineRangeFilter filter(underlines, start(), end());
1317 for (CompositionUnderlineRangeFilter::ConstIterator it = filter.begin(); it != filter.end(); ++it) {
1318 if (it->backgroundColor == Color::transparent)
1319 continue;
1320 paintSingleCompositionBackgroundRun(pt, boxOrigin, style, font, it->backgroundColor, underlinePaintStart(*it), underlinePaintEnd(*it));
1321 }
1322
1323 } else {
1324 paintSingleCompositionBackgroundRun(pt, boxOrigin, style, font, RenderTheme::theme().platformDefaultCompositionBackgroundColor(),
1325 renderer().frame()->inputMethodController().compositionStart(),
1326 renderer().frame()->inputMethodController().compositionEnd());
1327 }
1328 }
1329
paintDocumentMarkers(GraphicsContext * pt,const FloatPoint & boxOrigin,RenderStyle * style,const Font & font,bool background)1330 void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, bool background)
1331 {
1332 if (!renderer().node())
1333 return;
1334
1335 WillBeHeapVector<DocumentMarker*> markers = renderer().document().markers().markersFor(renderer().node());
1336 WillBeHeapVector<DocumentMarker*>::const_iterator markerIt = markers.begin();
1337
1338 // Give any document markers that touch this run a chance to draw before the text has been drawn.
1339 // Note end() points at the last char, not one past it like endOffset and ranges do.
1340 for ( ; markerIt != markers.end(); ++markerIt) {
1341 DocumentMarker* marker = *markerIt;
1342
1343 // Paint either the background markers or the foreground markers, but not both
1344 switch (marker->type()) {
1345 case DocumentMarker::Grammar:
1346 case DocumentMarker::Spelling:
1347 if (background)
1348 continue;
1349 break;
1350 case DocumentMarker::TextMatch:
1351 if (!background)
1352 continue;
1353 break;
1354 default:
1355 continue;
1356 }
1357
1358 if (marker->endOffset() <= start())
1359 // marker is completely before this run. This might be a marker that sits before the
1360 // first run we draw, or markers that were within runs we skipped due to truncation.
1361 continue;
1362
1363 if (marker->startOffset() > end())
1364 // marker is completely after this run, bail. A later run will paint it.
1365 break;
1366
1367 // marker intersects this run. Paint it.
1368 switch (marker->type()) {
1369 case DocumentMarker::Spelling:
1370 paintDocumentMarker(pt, boxOrigin, marker, style, font, false);
1371 break;
1372 case DocumentMarker::Grammar:
1373 paintDocumentMarker(pt, boxOrigin, marker, style, font, true);
1374 break;
1375 case DocumentMarker::TextMatch:
1376 paintTextMatchMarker(pt, boxOrigin, marker, style, font);
1377 break;
1378 default:
1379 ASSERT_NOT_REACHED();
1380 }
1381
1382 }
1383 }
1384
paintCompositionUnderline(GraphicsContext * ctx,const FloatPoint & boxOrigin,const CompositionUnderline & underline)1385 void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const FloatPoint& boxOrigin, const CompositionUnderline& underline)
1386 {
1387 if (m_truncation == cFullTruncation)
1388 return;
1389
1390 unsigned paintStart = underlinePaintStart(underline);
1391 unsigned paintEnd = underlinePaintEnd(underline);
1392
1393 // start of line to draw, relative to paintOffset.
1394 float start = paintStart == static_cast<unsigned>(m_start) ? 0 :
1395 toRenderText(renderer()).width(m_start, paintStart - m_start, textPos(), isLeftToRightDirection() ? LTR : RTL, isFirstLineStyle());
1396 // how much line to draw
1397 float width = (paintStart == static_cast<unsigned>(m_start) && paintEnd == static_cast<unsigned>(end()) + 1) ? m_logicalWidth :
1398 toRenderText(renderer()).width(paintStart, paintEnd - paintStart, textPos() + start, isLeftToRightDirection() ? LTR : RTL, isFirstLineStyle());
1399
1400 // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
1401 // All other marked text underlines are 1px thick.
1402 // If there's not enough space the underline will touch or overlap characters.
1403 int lineThickness = 1;
1404 int baseline = renderer().style(isFirstLineStyle())->fontMetrics().ascent();
1405 if (underline.thick && logicalHeight() - baseline >= 2)
1406 lineThickness = 2;
1407
1408 // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
1409 // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
1410 start += 1;
1411 width -= 2;
1412
1413 ctx->setStrokeColor(underline.color);
1414 ctx->setStrokeThickness(lineThickness);
1415 ctx->drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, textRenderer().document().printing());
1416 }
1417
caretMinOffset() const1418 int InlineTextBox::caretMinOffset() const
1419 {
1420 return m_start;
1421 }
1422
caretMaxOffset() const1423 int InlineTextBox::caretMaxOffset() const
1424 {
1425 return m_start + m_len;
1426 }
1427
textPos() const1428 float InlineTextBox::textPos() const
1429 {
1430 // When computing the width of a text run, RenderBlock::computeInlineDirectionPositionsForLine() doesn't include the actual offset
1431 // from the containing block edge in its measurement. textPos() should be consistent so the text are rendered in the same width.
1432 if (logicalLeft() == 0)
1433 return 0;
1434 return logicalLeft() - root().logicalLeft();
1435 }
1436
offsetForPosition(float lineOffset,bool includePartialGlyphs) const1437 int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs) const
1438 {
1439 if (isLineBreak())
1440 return 0;
1441
1442 if (lineOffset - logicalLeft() > logicalWidth())
1443 return isLeftToRightDirection() ? len() : 0;
1444 if (lineOffset - logicalLeft() < 0)
1445 return isLeftToRightDirection() ? 0 : len();
1446
1447 FontCachePurgePreventer fontCachePurgePreventer;
1448
1449 RenderText& text = toRenderText(renderer());
1450 RenderStyle* style = text.style(isFirstLineStyle());
1451 const Font& font = style->font();
1452 return font.offsetForPosition(constructTextRun(style, font), lineOffset - logicalLeft(), includePartialGlyphs);
1453 }
1454
positionForOffset(int offset) const1455 float InlineTextBox::positionForOffset(int offset) const
1456 {
1457 ASSERT(offset >= m_start);
1458 ASSERT(offset <= m_start + m_len);
1459
1460 if (isLineBreak())
1461 return logicalLeft();
1462
1463 FontCachePurgePreventer fontCachePurgePreventer;
1464
1465 RenderText& text = toRenderText(renderer());
1466 RenderStyle* styleToUse = text.style(isFirstLineStyle());
1467 ASSERT(styleToUse);
1468 const Font& font = styleToUse->font();
1469 int from = !isLeftToRightDirection() ? offset - m_start : 0;
1470 int to = !isLeftToRightDirection() ? m_len : offset - m_start;
1471 // FIXME: Do we need to add rightBearing here?
1472 return font.selectionRectForText(constructTextRun(styleToUse, font), IntPoint(logicalLeft(), 0), 0, from, to).maxX();
1473 }
1474
containsCaretOffset(int offset) const1475 bool InlineTextBox::containsCaretOffset(int offset) const
1476 {
1477 // Offsets before the box are never "in".
1478 if (offset < m_start)
1479 return false;
1480
1481 int pastEnd = m_start + m_len;
1482
1483 // Offsets inside the box (not at either edge) are always "in".
1484 if (offset < pastEnd)
1485 return true;
1486
1487 // Offsets outside the box are always "out".
1488 if (offset > pastEnd)
1489 return false;
1490
1491 // Offsets at the end are "out" for line breaks (they are on the next line).
1492 if (isLineBreak())
1493 return false;
1494
1495 // Offsets at the end are "in" for normal boxes (but the caller has to check affinity).
1496 return true;
1497 }
1498
characterWidths(Vector<float> & widths) const1499 void InlineTextBox::characterWidths(Vector<float>& widths) const
1500 {
1501 FontCachePurgePreventer fontCachePurgePreventer;
1502
1503 RenderStyle* styleToUse = textRenderer().style(isFirstLineStyle());
1504 const Font& font = styleToUse->font();
1505
1506 TextRun textRun = constructTextRun(styleToUse, font);
1507
1508 GlyphBuffer glyphBuffer;
1509 WidthIterator it(&font, textRun);
1510 float lastWidth = 0;
1511 widths.resize(m_len);
1512 for (unsigned i = 0; i < m_len; i++) {
1513 it.advance(i + 1, &glyphBuffer);
1514 widths[i] = it.m_runWidthSoFar - lastWidth;
1515 lastWidth = it.m_runWidthSoFar;
1516 }
1517 }
1518
constructTextRun(RenderStyle * style,const Font & font,StringBuilder * charactersWithHyphen) const1519 TextRun InlineTextBox::constructTextRun(RenderStyle* style, const Font& font, StringBuilder* charactersWithHyphen) const
1520 {
1521 ASSERT(style);
1522 ASSERT(textRenderer().text());
1523
1524 StringView string = textRenderer().text().createView();
1525 unsigned startPos = start();
1526 unsigned length = len();
1527
1528 if (string.length() != length || startPos)
1529 string.narrow(startPos, length);
1530
1531 return constructTextRun(style, font, string, textRenderer().textLength() - startPos, charactersWithHyphen);
1532 }
1533
constructTextRun(RenderStyle * style,const Font & font,StringView string,int maximumLength,StringBuilder * charactersWithHyphen) const1534 TextRun InlineTextBox::constructTextRun(RenderStyle* style, const Font& font, StringView string, int maximumLength, StringBuilder* charactersWithHyphen) const
1535 {
1536 ASSERT(style);
1537
1538 if (charactersWithHyphen) {
1539 const AtomicString& hyphenString = style->hyphenString();
1540 charactersWithHyphen->reserveCapacity(string.length() + hyphenString.length());
1541 charactersWithHyphen->append(string);
1542 charactersWithHyphen->append(hyphenString);
1543 string = charactersWithHyphen->toString().createView();
1544 maximumLength = string.length();
1545 }
1546
1547 ASSERT(maximumLength >= static_cast<int>(string.length()));
1548
1549 TextRun run(string, textPos(), expansion(), expansionBehavior(), direction(), dirOverride() || style->rtlOrdering() == VisualOrder, !textRenderer().canUseSimpleFontCodePath());
1550 run.setTabSize(!style->collapseWhiteSpace(), style->tabSize());
1551 run.setCharacterScanForCodePath(!textRenderer().canUseSimpleFontCodePath());
1552 if (textRunNeedsRenderingContext(font))
1553 run.setRenderingContext(SVGTextRunRenderingContext::create(&textRenderer()));
1554
1555 // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring.
1556 run.setCharactersLength(maximumLength);
1557 ASSERT(run.charactersLength() >= run.length());
1558 return run;
1559 }
1560
constructTextRunForInspector(RenderStyle * style,const Font & font) const1561 TextRun InlineTextBox::constructTextRunForInspector(RenderStyle* style, const Font& font) const
1562 {
1563 return InlineTextBox::constructTextRun(style, font);
1564 }
1565
1566 #ifndef NDEBUG
1567
boxName() const1568 const char* InlineTextBox::boxName() const
1569 {
1570 return "InlineTextBox";
1571 }
1572
showBox(int printedCharacters) const1573 void InlineTextBox::showBox(int printedCharacters) const
1574 {
1575 const RenderText& obj = toRenderText(renderer());
1576 String value = obj.text();
1577 value = value.substring(start(), len());
1578 value.replaceWithLiteral('\\', "\\\\");
1579 value.replaceWithLiteral('\n', "\\n");
1580 printedCharacters += fprintf(stderr, "%s\t%p", boxName(), this);
1581 for (; printedCharacters < showTreeCharacterOffset; printedCharacters++)
1582 fputc(' ', stderr);
1583 printedCharacters = fprintf(stderr, "\t%s %p", obj.renderName(), &obj);
1584 const int rendererCharacterOffset = 24;
1585 for (; printedCharacters < rendererCharacterOffset; printedCharacters++)
1586 fputc(' ', stderr);
1587 fprintf(stderr, "(%d,%d) \"%s\"\n", start(), start() + len(), value.utf8().data());
1588 }
1589
1590 #endif
1591
1592 } // namespace WebCore
1593