1 | /* |
2 | * (C) 1999 Lars Knoll (knoll@kde.org) |
3 | * (C) 2000 Dirk Mueller (mueller@kde.org) |
4 | * Copyright (C) 2004-2017 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 "TextPainter.h" |
25 | |
26 | #include "DisplayListReplayer.h" |
27 | #include "FilterOperations.h" |
28 | #include "GraphicsContext.h" |
29 | #include "InlineTextBox.h" |
30 | #include "RenderCombineText.h" |
31 | #include "RenderLayer.h" |
32 | #include "ShadowData.h" |
33 | #include <wtf/NeverDestroyed.h> |
34 | |
35 | namespace WebCore { |
36 | |
37 | ShadowApplier::ShadowApplier(GraphicsContext& context, const ShadowData* shadow, const FilterOperations* colorFilter, const FloatRect& textRect, bool lastShadowIterationShouldDrawText, bool opaque, FontOrientation orientation) |
38 | : m_context { context } |
39 | , m_shadow { shadow } |
40 | , m_onlyDrawsShadow { !isLastShadowIteration() || !lastShadowIterationShouldDrawText } |
41 | , m_avoidDrawingShadow { shadowIsCompletelyCoveredByText(opaque) } |
42 | , m_nothingToDraw { shadow && m_avoidDrawingShadow && m_onlyDrawsShadow } |
43 | , m_didSaveContext { false } |
44 | { |
45 | if (!shadow || m_nothingToDraw) { |
46 | m_shadow = nullptr; |
47 | return; |
48 | } |
49 | |
50 | int shadowX = orientation == FontOrientation::Horizontal ? shadow->x() : shadow->y(); |
51 | int shadowY = orientation == FontOrientation::Horizontal ? shadow->y() : -shadow->x(); |
52 | FloatSize shadowOffset(shadowX, shadowY); |
53 | int shadowRadius = shadow->radius(); |
54 | Color shadowColor = shadow->color(); |
55 | if (colorFilter) |
56 | colorFilter->transformColor(shadowColor); |
57 | |
58 | // When drawing shadows, we usually clip the context to the area the shadow will reside, and then |
59 | // draw the text itself outside the clipped area (so only the shadow shows up). However, we can |
60 | // often draw the *last* shadow and the text itself in a single call. |
61 | if (m_onlyDrawsShadow) { |
62 | FloatRect shadowRect(textRect); |
63 | shadowRect.inflate(shadow->paintingExtent() + 3 * textRect.height()); |
64 | shadowRect.move(shadowOffset); |
65 | context.save(); |
66 | context.clip(shadowRect); |
67 | |
68 | m_didSaveContext = true; |
69 | m_extraOffset = FloatSize(0, 2 * shadowRect.height() + std::max(0.0f, shadowOffset.height()) + shadowRadius); |
70 | shadowOffset -= m_extraOffset; |
71 | } |
72 | |
73 | if (!m_avoidDrawingShadow) |
74 | context.setShadow(shadowOffset, shadowRadius, shadowColor); |
75 | } |
76 | |
77 | inline bool ShadowApplier::isLastShadowIteration() |
78 | { |
79 | return m_shadow && !m_shadow->next(); |
80 | } |
81 | |
82 | inline bool ShadowApplier::shadowIsCompletelyCoveredByText(bool textIsOpaque) |
83 | { |
84 | return textIsOpaque && m_shadow && m_shadow->location().isZero() && !m_shadow->radius(); |
85 | } |
86 | |
87 | ShadowApplier::~ShadowApplier() |
88 | { |
89 | if (!m_shadow) |
90 | return; |
91 | if (m_onlyDrawsShadow) |
92 | m_context.restore(); |
93 | else if (!m_avoidDrawingShadow) |
94 | m_context.clearShadow(); |
95 | } |
96 | |
97 | TextPainter::TextPainter(GraphicsContext& context) |
98 | : m_context(context) |
99 | { |
100 | } |
101 | |
102 | void TextPainter::paintTextOrEmphasisMarks(const FontCascade& font, const TextRun& textRun, const AtomicString& emphasisMark, |
103 | float emphasisMarkOffset, const FloatPoint& textOrigin, unsigned startOffset, unsigned endOffset) |
104 | { |
105 | ASSERT(startOffset < endOffset); |
106 | if (!emphasisMark.isEmpty()) |
107 | m_context.drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + FloatSize(0, emphasisMarkOffset), startOffset, endOffset); |
108 | else if (startOffset || endOffset < textRun.length() || !m_glyphDisplayList) |
109 | m_context.drawText(font, textRun, textOrigin, startOffset, endOffset); |
110 | else { |
111 | // Replaying back a whole cached glyph run to the GraphicsContext. |
112 | m_context.translate(textOrigin); |
113 | DisplayList::Replayer replayer(m_context, *m_glyphDisplayList); |
114 | replayer.replay(); |
115 | m_context.translate(-textOrigin); |
116 | } |
117 | m_glyphDisplayList = nullptr; |
118 | } |
119 | |
120 | void TextPainter::paintTextWithShadows(const ShadowData* shadow, const FilterOperations* colorFilter, const FontCascade& font, const TextRun& textRun, const FloatRect& boxRect, const FloatPoint& textOrigin, |
121 | unsigned startOffset, unsigned endOffset, const AtomicString& emphasisMark, float emphasisMarkOffset, bool stroked) |
122 | { |
123 | if (!shadow) { |
124 | paintTextOrEmphasisMarks(font, textRun, emphasisMark, emphasisMarkOffset, textOrigin, startOffset, endOffset); |
125 | return; |
126 | } |
127 | |
128 | Color fillColor = m_context.fillColor(); |
129 | bool opaque = fillColor.isOpaque(); |
130 | bool lastShadowIterationShouldDrawText = !stroked && opaque; |
131 | if (!opaque) |
132 | m_context.setFillColor(Color::black); |
133 | while (shadow) { |
134 | ShadowApplier shadowApplier(m_context, shadow, colorFilter, boxRect, lastShadowIterationShouldDrawText, opaque, m_textBoxIsHorizontal ? FontOrientation::Horizontal : FontOrientation::Vertical); |
135 | if (!shadowApplier.nothingToDraw()) |
136 | paintTextOrEmphasisMarks(font, textRun, emphasisMark, emphasisMarkOffset, textOrigin + shadowApplier.extraOffset(), startOffset, endOffset); |
137 | shadow = shadow->next(); |
138 | } |
139 | |
140 | if (!lastShadowIterationShouldDrawText) { |
141 | if (!opaque) |
142 | m_context.setFillColor(fillColor); |
143 | paintTextOrEmphasisMarks(font, textRun, emphasisMark, emphasisMarkOffset, textOrigin, startOffset, endOffset); |
144 | } |
145 | } |
146 | |
147 | void TextPainter::paintTextAndEmphasisMarksIfNeeded(const TextRun& textRun, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned startOffset, unsigned endOffset, |
148 | const TextPaintStyle& paintStyle, const ShadowData* shadow, const FilterOperations* shadowColorFilter) |
149 | { |
150 | if (paintStyle.paintOrder == PaintOrder::Normal) { |
151 | // FIXME: Truncate right-to-left text correctly. |
152 | paintTextWithShadows(shadow, shadowColorFilter, *m_font, textRun, boxRect, textOrigin, startOffset, endOffset, nullAtom(), 0, paintStyle.strokeWidth > 0); |
153 | } else { |
154 | auto textDrawingMode = m_context.textDrawingMode(); |
155 | auto paintOrder = RenderStyle::paintTypesForPaintOrder(paintStyle.paintOrder); |
156 | auto shadowToUse = shadow; |
157 | |
158 | for (auto order : paintOrder) { |
159 | switch (order) { |
160 | case PaintType::Fill: |
161 | m_context.setTextDrawingMode(textDrawingMode & ~TextModeStroke); |
162 | paintTextWithShadows(shadowToUse, shadowColorFilter, *m_font, textRun, boxRect, textOrigin, startOffset, endOffset, nullAtom(), 0, false); |
163 | shadowToUse = nullptr; |
164 | m_context.setTextDrawingMode(textDrawingMode); |
165 | break; |
166 | case PaintType::Stroke: |
167 | m_context.setTextDrawingMode(textDrawingMode & ~TextModeFill); |
168 | paintTextWithShadows(shadowToUse, shadowColorFilter, *m_font, textRun, boxRect, textOrigin, startOffset, endOffset, nullAtom(), 0, paintStyle.strokeWidth > 0); |
169 | shadowToUse = nullptr; |
170 | m_context.setTextDrawingMode(textDrawingMode); |
171 | break; |
172 | case PaintType::Markers: |
173 | continue; |
174 | } |
175 | } |
176 | } |
177 | |
178 | if (m_emphasisMark.isEmpty()) |
179 | return; |
180 | |
181 | FloatPoint boxOrigin = boxRect.location(); |
182 | updateGraphicsContext(m_context, paintStyle, UseEmphasisMarkColor); |
183 | static NeverDestroyed<TextRun> objectReplacementCharacterTextRun(StringView(&objectReplacementCharacter, 1)); |
184 | const TextRun& emphasisMarkTextRun = m_combinedText ? objectReplacementCharacterTextRun.get() : textRun; |
185 | FloatPoint emphasisMarkTextOrigin = m_combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + m_font->fontMetrics().ascent()) : textOrigin; |
186 | if (m_combinedText) |
187 | m_context.concatCTM(rotation(boxRect, Clockwise)); |
188 | |
189 | // FIXME: Truncate right-to-left text correctly. |
190 | paintTextWithShadows(shadow, shadowColorFilter, m_combinedText ? m_combinedText->originalFont() : *m_font, emphasisMarkTextRun, boxRect, emphasisMarkTextOrigin, startOffset, endOffset, |
191 | m_emphasisMark, m_emphasisMarkOffset, paintStyle.strokeWidth > 0); |
192 | |
193 | if (m_combinedText) |
194 | m_context.concatCTM(rotation(boxRect, Counterclockwise)); |
195 | } |
196 | |
197 | void TextPainter::paint(const TextRun& textRun, const FloatRect& boxRect, const FloatPoint& textOrigin) |
198 | { |
199 | paintRange(textRun, boxRect, textOrigin, 0, textRun.length()); |
200 | } |
201 | |
202 | void TextPainter::paintRange(const TextRun& textRun, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned start, unsigned end) |
203 | { |
204 | ASSERT(m_font); |
205 | ASSERT(start < end); |
206 | |
207 | GraphicsContextStateSaver stateSaver(m_context, m_style.strokeWidth > 0); |
208 | updateGraphicsContext(m_context, m_style); |
209 | paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, start, end, m_style, m_shadow, m_shadowColorFilter); |
210 | } |
211 | |
212 | void TextPainter::clearGlyphDisplayLists() |
213 | { |
214 | GlyphDisplayListCache<InlineTextBox>::singleton().clear(); |
215 | GlyphDisplayListCache<SimpleLineLayout::Run>::singleton().clear(); |
216 | } |
217 | |
218 | bool TextPainter::shouldUseGlyphDisplayList(const PaintInfo& paintInfo) |
219 | { |
220 | return !paintInfo.context().paintingDisabled() && paintInfo.enclosingSelfPaintingLayer() && paintInfo.enclosingSelfPaintingLayer()->paintingFrequently(); |
221 | } |
222 | |
223 | } // namespace WebCore |
224 | |