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