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.
4
5#include "config.h"
6#include "core/rendering/TextPainter.h"
7
8#include "core/CSSPropertyNames.h"
9#include "core/frame/Settings.h"
10#include "core/rendering/InlineTextBox.h"
11#include "core/rendering/RenderCombineText.h"
12#include "core/rendering/RenderObject.h"
13#include "core/rendering/style/RenderStyle.h"
14#include "core/rendering/style/ShadowList.h"
15#include "platform/fonts/Font.h"
16#include "platform/graphics/GraphicsContext.h"
17#include "platform/graphics/GraphicsContextStateSaver.h"
18#include "platform/text/TextRun.h"
19#include "wtf/Assertions.h"
20#include "wtf/unicode/CharacterNames.h"
21
22namespace blink {
23
24TextPainter::TextPainter(GraphicsContext* context, const Font& font, const TextRun& run, const FloatPoint& textOrigin, const FloatRect& textBounds, bool horizontal)
25    : m_graphicsContext(context)
26    , m_font(font)
27    , m_run(run)
28    , m_textOrigin(textOrigin)
29    , m_textBounds(textBounds)
30    , m_horizontal(horizontal)
31    , m_emphasisMarkOffset(0)
32    , m_combinedText(0)
33{
34}
35
36TextPainter::~TextPainter()
37{
38}
39
40void TextPainter::setEmphasisMark(const AtomicString& emphasisMark, TextEmphasisPosition position)
41{
42    m_emphasisMark = emphasisMark;
43
44    if (emphasisMark.isNull()) {
45        m_emphasisMarkOffset = 0;
46    } else if (position == TextEmphasisPositionOver) {
47        m_emphasisMarkOffset = -m_font.fontMetrics().ascent() - m_font.emphasisMarkDescent(emphasisMark);
48    } else {
49        ASSERT(position == TextEmphasisPositionUnder);
50        m_emphasisMarkOffset = m_font.fontMetrics().descent() + m_font.emphasisMarkAscent(emphasisMark);
51    }
52}
53
54void TextPainter::paint(int startOffset, int endOffset, int length, const Style& textStyle, TextBlobPtr* cachedTextBlob)
55{
56    GraphicsContextStateSaver stateSaver(*m_graphicsContext, false);
57    updateGraphicsContext(textStyle, stateSaver);
58    paintInternal<PaintText>(startOffset, endOffset, length, cachedTextBlob);
59
60    if (!m_emphasisMark.isEmpty()) {
61        if (textStyle.emphasisMarkColor != textStyle.fillColor)
62            m_graphicsContext->setFillColor(textStyle.emphasisMarkColor);
63
64        if (m_combinedText)
65            paintEmphasisMarkForCombinedText();
66        else
67            paintInternal<PaintEmphasisMark>(startOffset, endOffset, length);
68    }
69}
70
71// static
72void TextPainter::updateGraphicsContext(GraphicsContext* context, const Style& textStyle, bool horizontal, GraphicsContextStateSaver& stateSaver)
73{
74    TextDrawingModeFlags mode = context->textDrawingMode();
75    if (textStyle.strokeWidth > 0) {
76        TextDrawingModeFlags newMode = mode | TextModeStroke;
77        if (mode != newMode) {
78            if (!stateSaver.saved())
79                stateSaver.save();
80            context->setTextDrawingMode(newMode);
81            mode = newMode;
82        }
83    }
84
85    if (mode & TextModeFill && textStyle.fillColor != context->fillColor())
86        context->setFillColor(textStyle.fillColor);
87
88    if (mode & TextModeStroke) {
89        if (textStyle.strokeColor != context->strokeColor())
90            context->setStrokeColor(textStyle.strokeColor);
91        if (textStyle.strokeWidth != context->strokeThickness())
92            context->setStrokeThickness(textStyle.strokeWidth);
93    }
94
95    // Text shadows are disabled when printing. http://crbug.com/258321
96    if (textStyle.shadow && !context->printing()) {
97        if (!stateSaver.saved())
98            stateSaver.save();
99        context->setDrawLooper(textStyle.shadow->createDrawLooper(DrawLooperBuilder::ShadowIgnoresAlpha, horizontal));
100    }
101}
102
103static Color textColorForWhiteBackground(Color textColor)
104{
105    int distanceFromWhite = differenceSquared(textColor, Color::white);
106    // semi-arbitrarily chose 65025 (255^2) value here after a few tests;
107    return distanceFromWhite > 65025 ? textColor : textColor.dark();
108}
109
110// static
111TextPainter::Style TextPainter::textPaintingStyle(RenderObject& renderer, RenderStyle* style, bool forceBlackText, bool isPrinting)
112{
113    TextPainter::Style textStyle;
114
115    if (forceBlackText) {
116        textStyle.fillColor = Color::black;
117        textStyle.strokeColor = Color::black;
118        textStyle.emphasisMarkColor = Color::black;
119        textStyle.strokeWidth = style->textStrokeWidth();
120        textStyle.shadow = 0;
121    } else {
122        textStyle.fillColor = renderer.resolveColor(style, CSSPropertyWebkitTextFillColor);
123        textStyle.strokeColor = renderer.resolveColor(style, CSSPropertyWebkitTextStrokeColor);
124        textStyle.emphasisMarkColor = renderer.resolveColor(style, CSSPropertyWebkitTextEmphasisColor);
125        textStyle.strokeWidth = style->textStrokeWidth();
126        textStyle.shadow = style->textShadow();
127
128        // Adjust text color when printing with a white background.
129        bool forceBackgroundToWhite = false;
130        if (isPrinting) {
131            if (style->printColorAdjust() == PrintColorAdjustEconomy)
132                forceBackgroundToWhite = true;
133            if (renderer.document().settings() && renderer.document().settings()->shouldPrintBackgrounds())
134                forceBackgroundToWhite = false;
135        }
136        if (forceBackgroundToWhite) {
137            textStyle.fillColor = textColorForWhiteBackground(textStyle.fillColor);
138            textStyle.strokeColor = textColorForWhiteBackground(textStyle.strokeColor);
139            textStyle.emphasisMarkColor = textColorForWhiteBackground(textStyle.emphasisMarkColor);
140        }
141
142        // Text shadows are disabled when printing. http://crbug.com/258321
143        if (isPrinting)
144            textStyle.shadow = 0;
145    }
146
147    return textStyle;
148}
149
150TextPainter::Style TextPainter::selectionPaintingStyle(RenderObject& renderer, bool haveSelection, bool forceBlackText, bool isPrinting, const TextPainter::Style& textStyle)
151{
152    TextPainter::Style selectionStyle = textStyle;
153
154    if (haveSelection) {
155        if (!forceBlackText) {
156            selectionStyle.fillColor = renderer.selectionForegroundColor();
157            selectionStyle.emphasisMarkColor = renderer.selectionEmphasisMarkColor();
158        }
159
160        if (RenderStyle* pseudoStyle = renderer.getCachedPseudoStyle(SELECTION)) {
161            selectionStyle.strokeColor = forceBlackText ? Color::black : renderer.resolveColor(pseudoStyle, CSSPropertyWebkitTextStrokeColor);
162            selectionStyle.strokeWidth = pseudoStyle->textStrokeWidth();
163            selectionStyle.shadow = forceBlackText ? 0 : pseudoStyle->textShadow();
164        }
165
166        // Text shadows are disabled when printing. http://crbug.com/258321
167        if (isPrinting)
168            selectionStyle.shadow = 0;
169    }
170
171    return selectionStyle;
172}
173
174static bool graphicsContextAllowsTextBlobs(GraphicsContext* context)
175{
176    // Text blobs affect the shader coordinate space.
177    // FIXME: Fix this, most likely in Skia.
178    return !context->strokeGradient() && !context->strokePattern() && !context->fillGradient() && !context->fillPattern();
179}
180
181template <TextPainter::PaintInternalStep step>
182void TextPainter::paintInternalRun(TextRunPaintInfo& textRunPaintInfo, int from, int to, TextBlobPtr* cachedTextBlob)
183{
184    textRunPaintInfo.from = from;
185    textRunPaintInfo.to = to;
186
187    if (step == PaintEmphasisMark) {
188        m_graphicsContext->drawEmphasisMarks(m_font, textRunPaintInfo, m_emphasisMark, m_textOrigin + IntSize(0, m_emphasisMarkOffset));
189        return;
190    }
191
192    ASSERT(step == PaintText);
193
194    TextBlobPtr localTextBlob;
195    TextBlobPtr& textBlob = cachedTextBlob ? *cachedTextBlob : localTextBlob;
196    bool canUseTextBlobs = RuntimeEnabledFeatures::textBlobEnabled() && graphicsContextAllowsTextBlobs(m_graphicsContext);
197
198    if (canUseTextBlobs && !textBlob)
199        textBlob = m_font.buildTextBlob(textRunPaintInfo, m_textOrigin, m_graphicsContext->couldUseLCDRenderedText());
200
201    if (canUseTextBlobs && textBlob)
202        m_font.drawTextBlob(m_graphicsContext, textBlob.get(), m_textOrigin.data());
203    else
204        m_graphicsContext->drawText(m_font, textRunPaintInfo, m_textOrigin);
205}
206
207template <TextPainter::PaintInternalStep Step>
208void TextPainter::paintInternal(int startOffset, int endOffset, int truncationPoint, TextBlobPtr* cachedTextBlob)
209{
210    // FIXME: We should be able to use cachedTextBlob in more cases.
211    TextRunPaintInfo textRunPaintInfo(m_run);
212    textRunPaintInfo.bounds = m_textBounds;
213    if (startOffset <= endOffset) {
214        paintInternalRun<Step>(textRunPaintInfo, startOffset, endOffset, cachedTextBlob);
215    } else {
216        if (endOffset > 0)
217            paintInternalRun<Step>(textRunPaintInfo, 0, endOffset);
218        if (startOffset < truncationPoint)
219            paintInternalRun<Step>(textRunPaintInfo, startOffset, truncationPoint);
220    }
221}
222
223void TextPainter::paintEmphasisMarkForCombinedText()
224{
225    ASSERT(m_combinedText);
226    DEFINE_STATIC_LOCAL(TextRun, placeholderTextRun, (&ideographicFullStop, 1));
227    FloatPoint emphasisMarkTextOrigin(m_textBounds.x(), m_textBounds.y() + m_font.fontMetrics().ascent() + m_emphasisMarkOffset);
228    TextRunPaintInfo textRunPaintInfo(placeholderTextRun);
229    textRunPaintInfo.bounds = m_textBounds;
230    m_graphicsContext->concatCTM(InlineTextBox::rotation(m_textBounds, InlineTextBox::Clockwise));
231    m_graphicsContext->drawEmphasisMarks(m_combinedText->originalFont(), textRunPaintInfo, m_emphasisMark, emphasisMarkTextOrigin);
232    m_graphicsContext->concatCTM(InlineTextBox::rotation(m_textBounds, InlineTextBox::Counterclockwise));
233}
234
235} // namespace blink
236