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
22 namespace blink {
23
TextPainter(GraphicsContext * context,const Font & font,const TextRun & run,const FloatPoint & textOrigin,const FloatRect & textBounds,bool horizontal)24 TextPainter::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
~TextPainter()36 TextPainter::~TextPainter()
37 {
38 }
39
setEmphasisMark(const AtomicString & emphasisMark,TextEmphasisPosition position)40 void 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
paint(int startOffset,int endOffset,int length,const Style & textStyle,TextBlobPtr * cachedTextBlob)54 void 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
updateGraphicsContext(GraphicsContext * context,const Style & textStyle,bool horizontal,GraphicsContextStateSaver & stateSaver)72 void 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
textColorForWhiteBackground(Color textColor)103 static 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
textPaintingStyle(RenderObject & renderer,RenderStyle * style,bool forceBlackText,bool isPrinting)111 TextPainter::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
selectionPaintingStyle(RenderObject & renderer,bool haveSelection,bool forceBlackText,bool isPrinting,const TextPainter::Style & textStyle)150 TextPainter::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
graphicsContextAllowsTextBlobs(GraphicsContext * context)174 static 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
181 template <TextPainter::PaintInternalStep step>
paintInternalRun(TextRunPaintInfo & textRunPaintInfo,int from,int to,TextBlobPtr * cachedTextBlob)182 void 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
207 template <TextPainter::PaintInternalStep Step>
paintInternal(int startOffset,int endOffset,int truncationPoint,TextBlobPtr * cachedTextBlob)208 void 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
paintEmphasisMarkForCombinedText()223 void 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