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 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 36 TextPainter::~TextPainter() 37 { 38 } 39 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 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 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 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 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 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 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> 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> 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 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