Move parseFontFaceDescriptor to CSSPropertyParser.cpp
[chromium-blink-merge.git] / third_party / WebKit / Source / core / paint / InlineTextBoxPainter.cpp
blob019aaf076a7c15b4895ea63a6281976ce61517ff
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "config.h"
6 #include "core/paint/InlineTextBoxPainter.h"
8 #include "core/editing/CompositionUnderline.h"
9 #include "core/editing/Editor.h"
10 #include "core/editing/markers/DocumentMarkerController.h"
11 #include "core/editing/markers/RenderedDocumentMarker.h"
12 #include "core/frame/LocalFrame.h"
13 #include "core/layout/LayoutBlock.h"
14 #include "core/layout/LayoutTextCombine.h"
15 #include "core/layout/LayoutTheme.h"
16 #include "core/layout/api/LineLayoutBox.h"
17 #include "core/layout/api/LineLayoutText.h"
18 #include "core/layout/line/InlineTextBox.h"
19 #include "core/paint/BoxPainter.h"
20 #include "core/paint/LayoutObjectDrawingRecorder.h"
21 #include "core/paint/PaintInfo.h"
22 #include "core/paint/TextPainter.h"
23 #include "platform/graphics/GraphicsContextStateSaver.h"
24 #include "wtf/Optional.h"
26 namespace blink {
28 typedef WTF::HashMap<const InlineTextBox*, TextBlobPtr> InlineTextBoxBlobCacheMap;
29 static InlineTextBoxBlobCacheMap* gTextBlobCache;
31 static const int misspellingLineThickness = 3;
33 void InlineTextBoxPainter::removeFromTextBlobCache(InlineTextBox& inlineTextBox)
35 if (gTextBlobCache)
36 gTextBlobCache->remove(&inlineTextBox);
39 static TextBlobPtr* addToTextBlobCache(InlineTextBox& inlineTextBox)
41 if (!gTextBlobCache)
42 gTextBlobCache = new InlineTextBoxBlobCacheMap;
43 return &gTextBlobCache->add(&inlineTextBox, nullptr).storedValue->value;
46 static bool paintsMarkerHighlights(const LayoutObject& layoutObject)
48 return layoutObject.node() && layoutObject.document().markers().hasMarkers(layoutObject.node());
51 static bool paintsCompositionMarkers(const LayoutObject& layoutObject)
53 return layoutObject.node() && layoutObject.document().markers().markersFor(layoutObject.node(), DocumentMarker::Composition).size() > 0;
56 void InlineTextBoxPainter::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset)
58 if (!shouldPaintTextBox(paintInfo))
59 return;
61 ASSERT(paintInfo.phase != PaintPhaseOutline && paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);
63 LayoutRect logicalVisualOverflow = m_inlineTextBox.logicalOverflowRect();
64 LayoutUnit logicalStart = logicalVisualOverflow.x() + (m_inlineTextBox.isHorizontal() ? paintOffset.x() : paintOffset.y());
65 LayoutUnit logicalExtent = logicalVisualOverflow.width();
67 LayoutUnit paintEnd = m_inlineTextBox.isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY();
68 LayoutUnit paintStart = m_inlineTextBox.isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y();
70 // We round the y-axis to ensure consistent line heights.
71 LayoutPoint adjustedPaintOffset = LayoutPoint(paintOffset.x(), paintOffset.y().round());
73 if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart)
74 return;
76 bool isPrinting = paintInfo.isPrinting();
78 // Determine whether or not we're selected.
79 bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && m_inlineTextBox.selectionState() != SelectionNone;
80 if (!haveSelection && paintInfo.phase == PaintPhaseSelection) {
81 // When only painting the selection, don't bother to paint if there is none.
82 return;
85 // The text clip phase already has a LayoutObjectDrawingRecorder. Text clips are initiated only in BoxPainter::paintLayerExtended,
86 // which is already within a LayoutObjectDrawingRecorder.
87 Optional<LayoutObjectDrawingRecorder> drawingRecorder;
88 if (paintInfo.phase != PaintPhaseTextClip) {
89 if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(*paintInfo.context, m_inlineTextBox, paintInfo.phase, paintOffset))
90 return;
91 LayoutRect paintRect(logicalVisualOverflow);
92 m_inlineTextBox.logicalRectToPhysicalRect(paintRect);
93 if (paintInfo.phase != PaintPhaseSelection && (haveSelection || paintsMarkerHighlights(m_inlineTextBox.layoutObject())))
94 paintRect.unite(m_inlineTextBox.localSelectionRect(m_inlineTextBox.start(), m_inlineTextBox.start() + m_inlineTextBox.len()));
95 paintRect.moveBy(adjustedPaintOffset);
96 drawingRecorder.emplace(*paintInfo.context, m_inlineTextBox, paintInfo.phase, paintRect, paintOffset);
99 if (m_inlineTextBox.truncation() != cNoTruncation) {
100 if (m_inlineTextBox.lineLayoutItem().containingBlock().style()->isLeftToRightDirection() != m_inlineTextBox.isLeftToRightDirection()) {
101 // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
102 // at which we start drawing text.
103 // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
104 // |Hello|CBA| -> |...He|CBA|
105 // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
106 // farther to the right.
107 // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
108 // truncated string i.e. |Hello|CBA| -> |...lo|CBA|
109 LayoutUnit widthOfVisibleText = m_inlineTextBox.lineLayoutItem().width(m_inlineTextBox.start(), m_inlineTextBox.truncation(), m_inlineTextBox.textPos(), m_inlineTextBox.isLeftToRightDirection() ? LTR : RTL, m_inlineTextBox.isFirstLineStyle());
110 LayoutUnit widthOfHiddenText = m_inlineTextBox.logicalWidth() - widthOfVisibleText;
111 // FIXME: The hit testing logic also needs to take this translation into account.
112 LayoutSize truncationOffset(m_inlineTextBox.isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0);
113 adjustedPaintOffset.move(m_inlineTextBox.isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
117 GraphicsContext* context = paintInfo.context;
118 const ComputedStyle& styleToUse = m_inlineTextBox.lineLayoutItem().styleRef(m_inlineTextBox.isFirstLineStyle());
120 LayoutPoint boxOrigin(m_inlineTextBox.locationIncludingFlipping());
121 boxOrigin.move(adjustedPaintOffset.x(), adjustedPaintOffset.y());
122 LayoutRect boxRect(boxOrigin, LayoutSize(m_inlineTextBox.logicalWidth(), m_inlineTextBox.logicalHeight()));
124 bool shouldRotate = false;
125 LayoutTextCombine* combinedText = nullptr;
126 if (!m_inlineTextBox.isHorizontal()) {
127 if (styleToUse.hasTextCombine() && m_inlineTextBox.lineLayoutItem().isCombineText()) {
128 combinedText = &toLayoutTextCombine(m_inlineTextBox.layoutObject());
129 if (!combinedText->isCombined())
130 combinedText = nullptr;
132 if (combinedText) {
133 combinedText->updateFont();
134 boxRect.setWidth(combinedText->inlineWidthForLayout());
135 } else {
136 shouldRotate = true;
137 context->concatCTM(TextPainter::rotation(boxRect, TextPainter::Clockwise));
141 // Determine text colors.
142 TextPainter::Style textStyle = TextPainter::textPaintingStyle(m_inlineTextBox.layoutObject(), styleToUse, paintInfo);
143 TextPainter::Style selectionStyle = TextPainter::selectionPaintingStyle(m_inlineTextBox.layoutObject(), haveSelection, paintInfo, textStyle);
144 bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection);
145 bool paintSelectedTextSeparately = !paintSelectedTextOnly && textStyle != selectionStyle;
147 // Set our font.
148 const Font& font = styleToUse.font();
150 LayoutPoint textOrigin(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());
152 // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection
153 // and composition highlights.
154 if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) {
155 paintDocumentMarkers(context, boxOrigin, styleToUse, font, true);
157 if (haveSelection && !paintsCompositionMarkers(m_inlineTextBox.layoutObject())) {
158 if (combinedText)
159 paintSelection<InlineTextBoxPainter::PaintOptions::CombinedText>(context, boxRect, styleToUse, font, selectionStyle.fillColor, combinedText);
160 else
161 paintSelection<InlineTextBoxPainter::PaintOptions::Normal>(context, boxRect, styleToUse, font, selectionStyle.fillColor);
165 // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only).
166 int length = m_inlineTextBox.len();
167 StringView string = m_inlineTextBox.lineLayoutItem().text().createView();
168 ASSERT(m_inlineTextBox.start() + length <= string.length());
169 if (static_cast<unsigned>(length) != string.length() || m_inlineTextBox.start())
170 string.narrow(m_inlineTextBox.start(), length);
171 int maximumLength = m_inlineTextBox.lineLayoutItem().textLength() - m_inlineTextBox.start();
173 StringBuilder charactersWithHyphen;
174 TextRun textRun = m_inlineTextBox.constructTextRun(styleToUse, font, string, maximumLength, m_inlineTextBox.hasHyphen() ? &charactersWithHyphen : 0);
175 if (m_inlineTextBox.hasHyphen())
176 length = textRun.length();
178 int selectionStart = 0;
179 int selectionEnd = 0;
180 if (paintSelectedTextOnly || paintSelectedTextSeparately)
181 m_inlineTextBox.selectionStartEnd(selectionStart, selectionEnd);
183 bool respectHyphen = selectionEnd == static_cast<int>(m_inlineTextBox.len()) && m_inlineTextBox.hasHyphen();
184 if (respectHyphen)
185 selectionEnd = textRun.length();
187 if (m_inlineTextBox.truncation() != cNoTruncation) {
188 selectionStart = std::min<int>(selectionStart, m_inlineTextBox.truncation());
189 selectionEnd = std::min<int>(selectionEnd, m_inlineTextBox.truncation());
190 length = m_inlineTextBox.truncation();
193 TextPainter textPainter(context, font, textRun, textOrigin, boxRect, m_inlineTextBox.isHorizontal());
194 TextEmphasisPosition emphasisMarkPosition;
195 bool hasTextEmphasis = m_inlineTextBox.getEmphasisMarkPosition(styleToUse, emphasisMarkPosition);
196 if (hasTextEmphasis)
197 textPainter.setEmphasisMark(styleToUse.textEmphasisMarkString(), emphasisMarkPosition);
198 if (combinedText)
199 textPainter.setCombinedText(combinedText);
201 if (!paintSelectedTextOnly) {
202 // FIXME: Truncate right-to-left text correctly.
203 int startOffset = 0;
204 int endOffset = length;
205 if (paintSelectedTextSeparately && selectionStart < selectionEnd) {
206 startOffset = selectionEnd;
207 endOffset = selectionStart;
210 // FIXME: This cache should probably ultimately be held somewhere else.
211 // A hashmap is convenient to avoid a memory hit when the
212 // RuntimeEnabledFeature is off.
213 bool textBlobIsCacheable = startOffset == 0 && endOffset == length;
214 TextBlobPtr* cachedTextBlob = 0;
215 if (textBlobIsCacheable)
216 cachedTextBlob = addToTextBlobCache(m_inlineTextBox);
217 textPainter.paint(startOffset, endOffset, length, textStyle, cachedTextBlob);
220 if ((paintSelectedTextOnly || paintSelectedTextSeparately) && selectionStart < selectionEnd) {
221 // paint only the text that is selected
222 bool textBlobIsCacheable = selectionStart == 0 && selectionEnd == length;
223 TextBlobPtr* cachedTextBlob = 0;
224 if (textBlobIsCacheable)
225 cachedTextBlob = addToTextBlobCache(m_inlineTextBox);
226 textPainter.paint(selectionStart, selectionEnd, length, selectionStyle, cachedTextBlob);
229 // Paint decorations
230 TextDecoration textDecorations = styleToUse.textDecorationsInEffect();
231 if (textDecorations != TextDecorationNone && !paintSelectedTextOnly) {
232 GraphicsContextStateSaver stateSaver(*context, false);
233 TextPainter::updateGraphicsContext(context, textStyle, m_inlineTextBox.isHorizontal(), stateSaver);
234 if (combinedText)
235 context->concatCTM(TextPainter::rotation(boxRect, TextPainter::Clockwise));
236 paintDecoration(paintInfo, boxOrigin, textDecorations);
237 if (combinedText)
238 context->concatCTM(TextPainter::rotation(boxRect, TextPainter::Counterclockwise));
241 if (paintInfo.phase == PaintPhaseForeground)
242 paintDocumentMarkers(context, boxOrigin, styleToUse, font, false);
244 if (shouldRotate)
245 context->concatCTM(TextPainter::rotation(boxRect, TextPainter::Counterclockwise));
248 bool InlineTextBoxPainter::shouldPaintTextBox(const PaintInfo& paintInfo)
250 // When painting selection, we want to include a highlight when the
251 // selection spans line breaks. In other cases such as invisible elements
252 // or those with no text that are not line breaks, we can skip painting
253 // wholesale.
254 // TODO(wkorman): Constrain line break painting to appropriate paint phase.
255 // This code path is only called in PaintPhaseForeground whereas we would
256 // expect PaintPhaseSelection. The existing haveSelection logic in paint()
257 // tests for != PaintPhaseTextClip.
258 bool paintLineBreaks = RuntimeEnabledFeatures::selectionPaintingWithoutSelectionGapsEnabled()
259 // TODO(wkorman): Remove horizontal and RTL restrictions once operational.
260 && m_inlineTextBox.isHorizontal()
261 && m_inlineTextBox.isLeftToRightDirection();
262 if ((!paintLineBreaks && m_inlineTextBox.isLineBreak())
263 || !paintInfo.shouldPaintWithinRoot(&m_inlineTextBox.layoutObject())
264 || m_inlineTextBox.lineLayoutItem().style()->visibility() != VISIBLE
265 || m_inlineTextBox.truncation() == cFullTruncation
266 || !m_inlineTextBox.len())
267 return false;
268 return true;
271 unsigned InlineTextBoxPainter::underlinePaintStart(const CompositionUnderline& underline)
273 return std::max(static_cast<unsigned>(m_inlineTextBox.start()), underline.startOffset);
276 unsigned InlineTextBoxPainter::underlinePaintEnd(const CompositionUnderline& underline)
278 unsigned paintEnd = std::min(m_inlineTextBox.end() + 1, underline.endOffset); // end() points at the last char, not past it.
279 if (m_inlineTextBox.truncation() != cNoTruncation)
280 paintEnd = std::min(paintEnd, static_cast<unsigned>(m_inlineTextBox.start() + m_inlineTextBox.truncation()));
281 return paintEnd;
284 void InlineTextBoxPainter::paintSingleCompositionBackgroundRun(GraphicsContext* context, const LayoutPoint& boxOrigin, const ComputedStyle& style, const Font& font, Color backgroundColor, int startPos, int endPos)
286 if (backgroundColor == Color::transparent)
287 return;
289 int sPos = std::max(startPos - static_cast<int>(m_inlineTextBox.start()), 0);
290 int ePos = std::min(endPos - static_cast<int>(m_inlineTextBox.start()), static_cast<int>(m_inlineTextBox.len()));
291 if (sPos >= ePos)
292 return;
294 int deltaY = m_inlineTextBox.lineLayoutItem().style()->isFlippedLinesWritingMode() ? m_inlineTextBox.root().selectionBottom() - m_inlineTextBox.logicalBottom() : m_inlineTextBox.logicalTop() - m_inlineTextBox.root().selectionTop();
295 int selHeight = m_inlineTextBox.root().selectionHeight();
296 FloatPoint localOrigin(boxOrigin.x().toFloat(), boxOrigin.y().toFloat() - deltaY);
297 context->drawHighlightForText(font, m_inlineTextBox.constructTextRun(style, font), localOrigin, selHeight, backgroundColor, sPos, ePos);
300 void InlineTextBoxPainter::paintDocumentMarkers(GraphicsContext* pt, const LayoutPoint& boxOrigin, const ComputedStyle& style, const Font& font, bool background)
302 if (!m_inlineTextBox.lineLayoutItem().node())
303 return;
305 DocumentMarkerVector markers = m_inlineTextBox.lineLayoutItem().document().markers().markersFor(m_inlineTextBox.lineLayoutItem().node());
306 DocumentMarkerVector::const_iterator markerIt = markers.begin();
308 // Give any document markers that touch this run a chance to draw before the text has been drawn.
309 // Note end() points at the last char, not one past it like endOffset and ranges do.
310 for ( ; markerIt != markers.end(); ++markerIt) {
311 DocumentMarker* marker = *markerIt;
313 // Paint either the background markers or the foreground markers, but not both
314 switch (marker->type()) {
315 case DocumentMarker::Grammar:
316 case DocumentMarker::Spelling:
317 if (background)
318 continue;
319 break;
320 case DocumentMarker::TextMatch:
321 if (!background)
322 continue;
323 break;
324 case DocumentMarker::Composition:
325 break;
326 default:
327 continue;
330 if (marker->endOffset() <= m_inlineTextBox.start()) {
331 // marker is completely before this run. This might be a marker that sits before the
332 // first run we draw, or markers that were within runs we skipped due to truncation.
333 continue;
335 if (marker->startOffset() > m_inlineTextBox.end()) {
336 // marker is completely after this run, bail. A later run will paint it.
337 break;
340 // marker intersects this run. Paint it.
341 switch (marker->type()) {
342 case DocumentMarker::Spelling:
343 m_inlineTextBox.paintDocumentMarker(pt, boxOrigin, marker, style, font, false);
344 break;
345 case DocumentMarker::Grammar:
346 m_inlineTextBox.paintDocumentMarker(pt, boxOrigin, marker, style, font, true);
347 break;
348 case DocumentMarker::TextMatch:
349 m_inlineTextBox.paintTextMatchMarker(pt, boxOrigin, marker, style, font);
350 break;
351 case DocumentMarker::Composition:
353 CompositionUnderline underline(marker->startOffset(), marker->endOffset(), marker->underlineColor(), marker->thick(), marker->backgroundColor());
354 if (background)
355 paintSingleCompositionBackgroundRun(pt, boxOrigin, style, font, underline.backgroundColor, underlinePaintStart(underline), underlinePaintEnd(underline));
356 else
357 paintCompositionUnderline(pt, boxOrigin, underline);
359 break;
360 default:
361 ASSERT_NOT_REACHED();
366 static GraphicsContext::DocumentMarkerLineStyle lineStyleForMarkerType(DocumentMarker::MarkerType markerType)
368 switch (markerType) {
369 case DocumentMarker::Spelling:
370 return GraphicsContext::DocumentMarkerSpellingLineStyle;
371 case DocumentMarker::Grammar:
372 return GraphicsContext::DocumentMarkerGrammarLineStyle;
373 default:
374 ASSERT_NOT_REACHED();
375 return GraphicsContext::DocumentMarkerSpellingLineStyle;
379 void InlineTextBoxPainter::paintDocumentMarker(GraphicsContext* pt, const LayoutPoint& boxOrigin, DocumentMarker* marker, const ComputedStyle& style, const Font& font, bool grammar)
381 // Never print spelling/grammar markers (5327887)
382 if (m_inlineTextBox.lineLayoutItem().document().printing())
383 return;
385 if (m_inlineTextBox.truncation() == cFullTruncation)
386 return;
388 LayoutUnit start = 0; // start of line to draw, relative to tx
389 LayoutUnit width = m_inlineTextBox.logicalWidth(); // how much line to draw
391 // Determine whether we need to measure text
392 bool markerSpansWholeBox = true;
393 if (m_inlineTextBox.start() <= marker->startOffset())
394 markerSpansWholeBox = false;
395 if ((m_inlineTextBox.end() + 1) != marker->endOffset()) // end points at the last char, not past it
396 markerSpansWholeBox = false;
397 if (m_inlineTextBox.truncation() != cNoTruncation)
398 markerSpansWholeBox = false;
400 if (!markerSpansWholeBox || grammar) {
401 int startPosition = std::max<int>(marker->startOffset() - m_inlineTextBox.start(), 0);
402 int endPosition = std::min<int>(marker->endOffset() - static_cast<int>(m_inlineTextBox.start()), m_inlineTextBox.len());
404 if (m_inlineTextBox.truncation() != cNoTruncation)
405 endPosition = std::min<int>(endPosition, m_inlineTextBox.truncation());
407 // Calculate start & width
408 int deltaY = m_inlineTextBox.lineLayoutItem().style()->isFlippedLinesWritingMode() ? m_inlineTextBox.root().selectionBottom() - m_inlineTextBox.logicalBottom() : m_inlineTextBox.logicalTop() - m_inlineTextBox.root().selectionTop();
409 int selHeight = m_inlineTextBox.root().selectionHeight();
410 LayoutPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY);
411 TextRun run = m_inlineTextBox.constructTextRun(style, font);
413 // FIXME: Convert the document markers to float rects.
414 IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, FloatPoint(startPoint), selHeight, startPosition, endPosition));
415 start = markerRect.x() - startPoint.x();
416 width = markerRect.width();
419 // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to
420 // make sure to fit within those bounds. This means the top pixel(s) of the underline will overlap the
421 // bottom pixel(s) of the glyphs in smaller font sizes. The alternatives are to increase the line spacing (bad!!)
422 // or decrease the underline thickness. The overlap is actually the most useful, and matches what AppKit does.
423 // So, we generally place the underline at the bottom of the text, but in larger fonts that's not so good so
424 // we pin to two pixels under the baseline.
425 int lineThickness = misspellingLineThickness;
426 int baseline = m_inlineTextBox.lineLayoutItem().style(m_inlineTextBox.isFirstLineStyle())->fontMetrics().ascent();
427 int descent = m_inlineTextBox.logicalHeight() - baseline;
428 int underlineOffset;
429 if (descent <= (lineThickness + 2)) {
430 // Place the underline at the very bottom of the text in small/medium fonts.
431 underlineOffset = m_inlineTextBox.logicalHeight() - lineThickness;
432 } else {
433 // In larger fonts, though, place the underline up near the baseline to prevent a big gap.
434 underlineOffset = baseline + 2;
436 pt->drawLineForDocumentMarker(FloatPoint((boxOrigin.x() + start).toFloat(), (boxOrigin.y() + underlineOffset).toFloat()), width.toFloat(), lineStyleForMarkerType(marker->type()));
439 template <InlineTextBoxPainter::PaintOptions options>
440 void InlineTextBoxPainter::paintSelection(GraphicsContext* context, const LayoutRect& boxRect, const ComputedStyle& style, const Font& font, Color textColor, LayoutTextCombine* combinedText)
442 // See if we have a selection to paint at all.
443 int sPos, ePos;
444 m_inlineTextBox.selectionStartEnd(sPos, ePos);
445 if (sPos >= ePos)
446 return;
448 Color c = m_inlineTextBox.lineLayoutItem().selectionBackgroundColor();
449 if (!c.alpha())
450 return;
452 // If the text color ends up being the same as the selection background, invert the selection
453 // background.
454 if (textColor == c)
455 c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
457 // If the text is truncated, let the thing being painted in the truncation
458 // draw its own highlight.
459 int length = m_inlineTextBox.truncation() != cNoTruncation ? m_inlineTextBox.truncation() : m_inlineTextBox.len();
460 StringView string = m_inlineTextBox.lineLayoutItem().text().createView();
462 if (string.length() != static_cast<unsigned>(length) || m_inlineTextBox.start())
463 string.narrow(m_inlineTextBox.start(), length);
465 StringBuilder charactersWithHyphen;
466 bool respectHyphen = ePos == length && m_inlineTextBox.hasHyphen();
467 TextRun textRun = m_inlineTextBox.constructTextRun(style, font, string, m_inlineTextBox.lineLayoutItem().textLength() - m_inlineTextBox.start(), respectHyphen ? &charactersWithHyphen : 0);
468 if (respectHyphen)
469 ePos = textRun.length();
471 GraphicsContextStateSaver stateSaver(*context);
473 if (options == InlineTextBoxPainter::PaintOptions::CombinedText) {
474 ASSERT(combinedText);
475 // We can't use the height of m_inlineTextBox because LayoutTextCombine's inlineTextBox is horizontal within vertical flow
476 LayoutRect clipRect(boxRect);
477 combinedText->transformLayoutRect(clipRect);
478 context->clip(FloatRect(clipRect));
479 combinedText->transformToInlineCoordinates(*context, boxRect);
480 context->drawHighlightForText(font, textRun, FloatPoint(boxRect.location()), boxRect.height(), c, sPos, ePos);
481 return;
484 LayoutUnit selectionBottom = m_inlineTextBox.root().selectionBottom();
485 LayoutUnit selectionTop = m_inlineTextBox.root().selectionTopAdjustedForPrecedingBlock();
487 int deltaY = roundToInt(m_inlineTextBox.lineLayoutItem().style()->isFlippedLinesWritingMode() ? selectionBottom - m_inlineTextBox.logicalBottom() : m_inlineTextBox.logicalTop() - selectionTop);
488 int selHeight = std::max(0, roundToInt(selectionBottom - selectionTop));
490 FloatPoint localOrigin(boxRect.x().toFloat(), (boxRect.y() - deltaY).toFloat());
492 LayoutUnit selectionWidth = m_inlineTextBox.logicalWidth();
493 LayoutRect selectionRect = LayoutRect(font.selectionRectForText(textRun, localOrigin, selHeight, sPos, ePos));
494 if (m_inlineTextBox.hasWrappedSelectionNewline()) {
495 expandToIncludeNewlineForSelection(selectionRect);
496 // TODO(wkorman): Make this work with RTL and vertical text.
497 selectionWidth += m_inlineTextBox.newlineSpaceWidth();
499 FloatRect clipRect(localOrigin, FloatSize(selectionWidth.toFloat(), selHeight));
500 // TODO(wkorman): Experiment with not clipping.
501 context->clip(clipRect);
502 context->fillRect(FloatRect(selectionRect), c);
505 void InlineTextBoxPainter::expandToIncludeNewlineForSelection(LayoutRect& rect)
507 // TODO(wkorman): Make this work with RTL and vertical text.
508 rect.expand(FloatRectOutsets(0, m_inlineTextBox.newlineSpaceWidth(), 0, 0));
511 static int computeUnderlineOffset(const TextUnderlinePosition underlinePosition, const FontMetrics& fontMetrics, const InlineTextBox* inlineTextBox, const float textDecorationThickness)
513 // Compute the gap between the font and the underline. Use at least one
514 // pixel gap, if underline is thick then use a bigger gap.
515 int gap = 0;
517 // Underline position of zero means draw underline on Baseline Position,
518 // in Blink we need at least 1-pixel gap to adding following check.
519 // Positive underline Position means underline should be drawn above baselin e
520 // and negative value means drawing below baseline, negating the value as in Blink
521 // downward Y-increases.
523 if (fontMetrics.underlinePosition())
524 gap = -fontMetrics.underlinePosition();
525 else
526 gap = std::max<int>(1, ceilf(textDecorationThickness / 2.f));
528 // FIXME: We support only horizontal text for now.
529 switch (underlinePosition) {
530 case TextUnderlinePositionAuto:
531 return fontMetrics.ascent() + gap; // Position underline near the alphabetic baseline.
532 case TextUnderlinePositionUnder: {
533 // Position underline relative to the under edge of the lowest element's content box.
534 const LayoutUnit offset = inlineTextBox->root().maxLogicalTop() - inlineTextBox->logicalTop();
535 if (offset > 0)
536 return inlineTextBox->logicalHeight() + gap + offset;
537 return inlineTextBox->logicalHeight() + gap;
541 ASSERT_NOT_REACHED();
542 return fontMetrics.ascent() + gap;
545 static bool shouldSetDecorationAntialias(TextDecorationStyle decorationStyle)
547 return decorationStyle == TextDecorationStyleDotted || decorationStyle == TextDecorationStyleDashed;
550 static bool shouldSetDecorationAntialias(TextDecorationStyle underline, TextDecorationStyle overline, TextDecorationStyle linethrough)
552 return shouldSetDecorationAntialias(underline) || shouldSetDecorationAntialias(overline) || shouldSetDecorationAntialias(linethrough);
555 static StrokeStyle textDecorationStyleToStrokeStyle(TextDecorationStyle decorationStyle)
557 StrokeStyle strokeStyle = SolidStroke;
558 switch (decorationStyle) {
559 case TextDecorationStyleSolid:
560 strokeStyle = SolidStroke;
561 break;
562 case TextDecorationStyleDouble:
563 strokeStyle = DoubleStroke;
564 break;
565 case TextDecorationStyleDotted:
566 strokeStyle = DottedStroke;
567 break;
568 case TextDecorationStyleDashed:
569 strokeStyle = DashedStroke;
570 break;
571 case TextDecorationStyleWavy:
572 strokeStyle = WavyStroke;
573 break;
576 return strokeStyle;
579 static void adjustStepToDecorationLength(float& step, float& controlPointDistance, float length)
581 ASSERT(step > 0);
583 if (length <= 0)
584 return;
586 unsigned stepCount = static_cast<unsigned>(length / step);
588 // Each Bezier curve starts at the same pixel that the previous one
589 // ended. We need to subtract (stepCount - 1) pixels when calculating the
590 // length covered to account for that.
591 float uncoveredLength = length - (stepCount * step - (stepCount - 1));
592 float adjustment = uncoveredLength / stepCount;
593 step += adjustment;
594 controlPointDistance += adjustment;
598 * Draw one cubic Bezier curve and repeat the same pattern long the the decoration's axis.
599 * The start point (p1), controlPoint1, controlPoint2 and end point (p2) of the Bezier curve
600 * form a diamond shape:
602 * step
603 * |-----------|
605 * controlPoint1
609 * . .
610 * . .
611 * . .
612 * (x1, y1) p1 + . + p2 (x2, y2) - <--- Decoration's axis
613 * . . |
614 * . . |
615 * . . | controlPointDistance
618 * + -
619 * controlPoint2
621 * |-----------|
622 * step
624 static void strokeWavyTextDecoration(GraphicsContext* context, FloatPoint p1, FloatPoint p2, float strokeThickness)
626 context->adjustLineToPixelBoundaries(p1, p2, strokeThickness, context->strokeStyle());
628 Path path;
629 path.moveTo(p1);
631 // Distance between decoration's axis and Bezier curve's control points.
632 // The height of the curve is based on this distance. Use a minimum of 6 pixels distance since
633 // the actual curve passes approximately at half of that distance, that is 3 pixels.
634 // The minimum height of the curve is also approximately 3 pixels. Increases the curve's height
635 // as strockThickness increases to make the curve looks better.
636 float controlPointDistance = 3 * std::max<float>(2, strokeThickness);
638 // Increment used to form the diamond shape between start point (p1), control
639 // points and end point (p2) along the axis of the decoration. Makes the
640 // curve wider as strockThickness increases to make the curve looks better.
641 float step = 2 * std::max<float>(2, strokeThickness);
643 bool isVerticalLine = (p1.x() == p2.x());
645 if (isVerticalLine) {
646 ASSERT(p1.x() == p2.x());
648 float xAxis = p1.x();
649 float y1;
650 float y2;
652 if (p1.y() < p2.y()) {
653 y1 = p1.y();
654 y2 = p2.y();
655 } else {
656 y1 = p2.y();
657 y2 = p1.y();
660 adjustStepToDecorationLength(step, controlPointDistance, y2 - y1);
661 FloatPoint controlPoint1(xAxis + controlPointDistance, 0);
662 FloatPoint controlPoint2(xAxis - controlPointDistance, 0);
664 for (float y = y1; y + 2 * step <= y2;) {
665 controlPoint1.setY(y + step);
666 controlPoint2.setY(y + step);
667 y += 2 * step;
668 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y));
670 } else {
671 ASSERT(p1.y() == p2.y());
673 float yAxis = p1.y();
674 float x1;
675 float x2;
677 if (p1.x() < p2.x()) {
678 x1 = p1.x();
679 x2 = p2.x();
680 } else {
681 x1 = p2.x();
682 x2 = p1.x();
685 adjustStepToDecorationLength(step, controlPointDistance, x2 - x1);
686 FloatPoint controlPoint1(0, yAxis + controlPointDistance);
687 FloatPoint controlPoint2(0, yAxis - controlPointDistance);
689 for (float x = x1; x + 2 * step <= x2;) {
690 controlPoint1.setX(x + step);
691 controlPoint2.setX(x + step);
692 x += 2 * step;
693 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis));
697 context->setShouldAntialias(true);
698 context->strokePath(path);
701 static void paintAppliedDecoration(GraphicsContext* context, FloatPoint start, float width, float doubleOffset, int wavyOffsetFactor,
702 LayoutObject::AppliedTextDecoration decoration, float thickness, bool antialiasDecoration, bool isPrinting)
704 context->setStrokeStyle(textDecorationStyleToStrokeStyle(decoration.style));
705 context->setStrokeColor(decoration.color);
707 switch (decoration.style) {
708 case TextDecorationStyleWavy:
709 strokeWavyTextDecoration(context, start + FloatPoint(0, doubleOffset * wavyOffsetFactor), start + FloatPoint(width, doubleOffset * wavyOffsetFactor), thickness);
710 break;
711 case TextDecorationStyleDotted:
712 case TextDecorationStyleDashed:
713 context->setShouldAntialias(antialiasDecoration);
714 // Fall through
715 default:
716 context->drawLineForText(FloatPoint(start), width, isPrinting);
718 if (decoration.style == TextDecorationStyleDouble)
719 context->drawLineForText(start + FloatPoint(0, doubleOffset), width, isPrinting);
723 void InlineTextBoxPainter::paintDecoration(const PaintInfo& paintInfo, const LayoutPoint& boxOrigin, TextDecoration deco)
725 if (m_inlineTextBox.truncation() == cFullTruncation)
726 return;
728 GraphicsContext* context = paintInfo.context;
729 GraphicsContextStateSaver stateSaver(*context);
731 LayoutPoint localOrigin(boxOrigin);
733 LayoutUnit width = m_inlineTextBox.logicalWidth();
734 if (m_inlineTextBox.truncation() != cNoTruncation) {
735 width = m_inlineTextBox.lineLayoutItem().width(m_inlineTextBox.start(), m_inlineTextBox.truncation(), m_inlineTextBox.textPos(), m_inlineTextBox.isLeftToRightDirection() ? LTR : RTL, m_inlineTextBox.isFirstLineStyle());
736 if (!m_inlineTextBox.isLeftToRightDirection())
737 localOrigin.move(m_inlineTextBox.logicalWidth() - width, 0);
740 // Get the text decoration colors.
741 LayoutObject::AppliedTextDecoration underline, overline, linethrough;
742 m_inlineTextBox.layoutObject().getTextDecorations(deco, underline, overline, linethrough, true);
743 if (m_inlineTextBox.isFirstLineStyle())
744 m_inlineTextBox.layoutObject().getTextDecorations(deco, underline, overline, linethrough, true, true);
746 // Use a special function for underlines to get the positioning exactly right.
747 bool isPrinting = paintInfo.isPrinting();
749 const ComputedStyle& styleToUse = m_inlineTextBox.lineLayoutItem().styleRef(m_inlineTextBox.isFirstLineStyle());
750 float baseline = styleToUse.fontMetrics().ascent();
752 // Set the thick of the line to be 10% (or something else ?)of the computed font size and not less than 1px.
753 // Using computedFontSize should take care of zoom as well.
755 // Update Underline thickness, in case we have Faulty Font Metrics calculating underline thickness by old method.
756 float textDecorationThickness = styleToUse.fontMetrics().underlineThickness();
757 int fontHeightInt = (int)(styleToUse.fontMetrics().floatHeight() + 0.5);
758 if ((textDecorationThickness == 0.f) || (textDecorationThickness >= (fontHeightInt >> 1)))
759 textDecorationThickness = std::max(1.f, styleToUse.computedFontSize() / 10.f);
761 context->setStrokeThickness(textDecorationThickness);
763 bool antialiasDecoration = shouldSetDecorationAntialias(overline.style, underline.style, linethrough.style);
765 // Offset between lines - always non-zero, so lines never cross each other.
766 float doubleOffset = textDecorationThickness + 1.f;
768 if (deco & TextDecorationUnderline) {
769 const int underlineOffset = computeUnderlineOffset(styleToUse.textUnderlinePosition(), styleToUse.fontMetrics(), &m_inlineTextBox, textDecorationThickness);
770 paintAppliedDecoration(context, FloatPoint(localOrigin) + FloatPoint(0, underlineOffset), width.toFloat(), doubleOffset, 1, underline, textDecorationThickness, antialiasDecoration, isPrinting);
772 if (deco & TextDecorationOverline) {
773 paintAppliedDecoration(context, FloatPoint(localOrigin), width.toFloat(), -doubleOffset, 1, overline, textDecorationThickness, antialiasDecoration, isPrinting);
775 if (deco & TextDecorationLineThrough) {
776 const float lineThroughOffset = 2 * baseline / 3;
777 paintAppliedDecoration(context, FloatPoint(localOrigin) + FloatPoint(0, lineThroughOffset), width.toFloat(), doubleOffset, 0, linethrough, textDecorationThickness, antialiasDecoration, isPrinting);
781 void InlineTextBoxPainter::paintCompositionUnderline(GraphicsContext* ctx, const LayoutPoint& boxOrigin, const CompositionUnderline& underline)
783 if (underline.color == Color::transparent)
784 return;
786 if (m_inlineTextBox.truncation() == cFullTruncation)
787 return;
789 unsigned paintStart = underlinePaintStart(underline);
790 unsigned paintEnd = underlinePaintEnd(underline);
792 // start of line to draw, relative to paintOffset.
793 float start = paintStart == static_cast<unsigned>(m_inlineTextBox.start()) ? 0 :
794 m_inlineTextBox.lineLayoutItem().width(m_inlineTextBox.start(), paintStart - m_inlineTextBox.start(), m_inlineTextBox.textPos(), m_inlineTextBox.isLeftToRightDirection() ? LTR : RTL, m_inlineTextBox.isFirstLineStyle());
795 // how much line to draw
796 float width = (paintStart == static_cast<unsigned>(m_inlineTextBox.start()) && paintEnd == static_cast<unsigned>(m_inlineTextBox.end()) + 1) ? m_inlineTextBox.logicalWidth().toFloat() :
797 m_inlineTextBox.lineLayoutItem().width(paintStart, paintEnd - paintStart, m_inlineTextBox.textPos() + start, m_inlineTextBox.isLeftToRightDirection() ? LTR : RTL, m_inlineTextBox.isFirstLineStyle());
799 // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline.
800 // All other marked text underlines are 1px thick.
801 // If there's not enough space the underline will touch or overlap characters.
802 int lineThickness = 1;
803 int baseline = m_inlineTextBox.lineLayoutItem().style(m_inlineTextBox.isFirstLineStyle())->fontMetrics().ascent();
804 if (underline.thick && m_inlineTextBox.logicalHeight() - baseline >= 2)
805 lineThickness = 2;
807 // We need to have some space between underlines of subsequent clauses, because some input methods do not use different underline styles for those.
808 // We make each line shorter, which has a harmless side effect of shortening the first and last clauses, too.
809 start += 1;
810 width -= 2;
812 ctx->setStrokeColor(underline.color);
813 ctx->setStrokeThickness(lineThickness);
814 ctx->drawLineForText(FloatPoint(boxOrigin.x() + start, (boxOrigin.y() + m_inlineTextBox.logicalHeight() - lineThickness).toFloat()), width, m_inlineTextBox.lineLayoutItem().document().printing());
817 void InlineTextBoxPainter::paintTextMatchMarker(GraphicsContext* pt, const LayoutPoint& boxOrigin, DocumentMarker* marker, const ComputedStyle& style, const Font& font)
819 // Use same y positioning and height as for selection, so that when the selection and this highlight are on
820 // the same word there are no pieces sticking out.
821 int deltaY = m_inlineTextBox.lineLayoutItem().style()->isFlippedLinesWritingMode() ? m_inlineTextBox.root().selectionBottom() - m_inlineTextBox.logicalBottom() : m_inlineTextBox.logicalTop() - m_inlineTextBox.root().selectionTop();
822 int selHeight = m_inlineTextBox.root().selectionHeight();
824 int sPos = std::max(marker->startOffset() - m_inlineTextBox.start(), (unsigned)0);
825 int ePos = std::min(marker->endOffset() - m_inlineTextBox.start(), m_inlineTextBox.len());
826 TextRun run = m_inlineTextBox.constructTextRun(style, font);
828 // Optionally highlight the text
829 if (m_inlineTextBox.layoutObject().frame()->editor().markedTextMatchesAreHighlighted()) {
830 Color color = marker->activeMatch() ?
831 LayoutTheme::theme().platformActiveTextSearchHighlightColor() :
832 LayoutTheme::theme().platformInactiveTextSearchHighlightColor();
833 GraphicsContextStateSaver stateSaver(*pt);
834 pt->clip(FloatRect(boxOrigin.x().toFloat(), (boxOrigin.y() - deltaY).toFloat(), m_inlineTextBox.logicalWidth().toFloat(), selHeight));
835 pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x().toFloat(), (boxOrigin.y() - deltaY).toFloat()), selHeight, color, sPos, ePos);
837 // Also Highlight the text with color:transparent
838 if (style.visitedDependentColor(CSSPropertyColor) == Color::transparent) {
839 int length = m_inlineTextBox.len();
840 TextPainter::Style textStyle;
841 // When we use the text as a clip, we only care about the alpha, thus we make all the colors black.
842 textStyle.currentColor = textStyle.fillColor = textStyle.strokeColor = textStyle.emphasisMarkColor = Color::black;
843 textStyle.strokeWidth = style.textStrokeWidth();
844 textStyle.shadow = 0;
846 LayoutRect boxRect(boxOrigin, LayoutSize(m_inlineTextBox.logicalWidth(), m_inlineTextBox.logicalHeight()));
847 LayoutPoint textOrigin(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent());
848 TextPainter textPainter(pt, font, run, textOrigin, boxRect, m_inlineTextBox.isHorizontal());
850 textPainter.paint(sPos, ePos, length, textStyle, 0);
856 } // namespace blink