12746f645cfab27eb8d959355bbec9b6b9896c2c
[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, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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 "TextPaintStyle.h"
30 #include <wtf/NeverDestroyed.h>
31
32 namespace WebCore {
33
34 TextPainter::TextPainter(GraphicsContext& context, bool paintSelectedTextOnly, bool paintSelectedTextSeparately, const FontCascade& font,
35     int startPositionInTextRun, int endPositionInTextBoxString, int length, const AtomicString& emphasisMark, RenderCombineText* combinedText, TextRun& textRun,
36     FloatRect& boxRect, FloatPoint& textOrigin, int emphasisMarkOffset, const ShadowData* textShadow, const ShadowData* selectionShadow,
37     bool textBoxIsHorizontal, TextPaintStyle& textPaintStyle, TextPaintStyle& selectionPaintStyle)
38         : m_context(context)
39         , m_textPaintStyle(textPaintStyle)
40         , m_selectionPaintStyle(selectionPaintStyle)
41         , m_textShadow(textShadow)
42         , m_selectionShadow(selectionShadow)
43         , m_paintSelectedTextOnly(paintSelectedTextOnly)
44         , m_paintSelectedTextSeparately(paintSelectedTextSeparately)
45         , m_font(font)
46         , m_startPositionInTextRun(startPositionInTextRun)
47         , m_endPositionInTextRun(endPositionInTextBoxString)
48         , m_length(length)
49         , m_emphasisMark(emphasisMark)
50         , m_combinedText(combinedText)
51         , m_textRun(textRun)
52         , m_boxRect(boxRect)
53         , m_textOrigin(textOrigin)
54         , m_emphasisMarkOffset(emphasisMarkOffset)
55         , m_textBoxIsHorizontal(textBoxIsHorizontal)
56 {
57 }
58
59 static void drawTextOrEmphasisMarks(GraphicsContext& context, const FontCascade& font, const TextRun& textRun, const AtomicString& emphasisMark,
60     int emphasisMarkOffset, const FloatPoint& point, const int from, const int to)
61 {
62     if (emphasisMark.isEmpty())
63         context.drawText(font, textRun, point, from, to);
64     else
65         context.drawEmphasisMarks(font, textRun, emphasisMark, point + IntSize(0, emphasisMarkOffset), from, to);
66 }
67
68 ShadowApplier::ShadowApplier(GraphicsContext& context, const ShadowData* shadow, const FloatRect& textRect, bool lastShadowIterationShouldDrawText, bool opaque, FontOrientation orientation)
69     : m_context(context)
70     , m_shadow(shadow)
71     , m_onlyDrawsShadow(!isLastShadowIteration() || !lastShadowIterationShouldDrawText)
72     , m_avoidDrawingShadow(shadowIsCompletelyCoveredByText(opaque))
73     , m_nothingToDraw(shadow && m_avoidDrawingShadow && m_onlyDrawsShadow)
74     , m_didSaveContext(false)
75 {
76     if (!shadow || m_nothingToDraw) {
77         m_shadow = nullptr;
78         return;
79     }
80
81     int shadowX = orientation == Horizontal ? shadow->x() : shadow->y();
82     int shadowY = orientation == Horizontal ? shadow->y() : -shadow->x();
83     FloatSize shadowOffset(shadowX, shadowY);
84     int shadowRadius = shadow->radius();
85     const Color& shadowColor = shadow->color();
86
87     // When drawing shadows, we usually clip the context to the area the shadow will reside, and then
88     // draw the text itself outside the clipped area (so only the shadow shows up). However, we can
89     // often draw the *last* shadow and the text itself in a single call.
90     if (m_onlyDrawsShadow) {
91         FloatRect shadowRect(textRect);
92         shadowRect.inflate(shadow->paintingExtent());
93         shadowRect.move(shadowOffset);
94         context.save();
95         context.clip(shadowRect);
96
97         m_didSaveContext = true;
98         m_extraOffset = FloatSize(0, 2 * textRect.height() + std::max(0.0f, shadowOffset.height()) + shadowRadius);
99         shadowOffset -= m_extraOffset;
100     }
101
102     if (!m_avoidDrawingShadow)
103         context.setShadow(shadowOffset, shadowRadius, shadowColor, context.fillColorSpace());
104 }
105
106 ShadowApplier::~ShadowApplier()
107 {
108     if (!m_shadow)
109         return;
110     if (m_onlyDrawsShadow)
111         m_context.restore();
112     else if (!m_avoidDrawingShadow)
113         m_context.clearShadow();
114 }
115
116 static void paintTextWithShadows(GraphicsContext& context, const FontCascade& font, const TextRun& textRun, const AtomicString& emphasisMark,
117     int emphasisMarkOffset, int startOffset, int endOffset, int truncationPoint, const FloatPoint& textOrigin, const FloatRect& boxRect,
118     const ShadowData* shadow, bool stroked, bool horizontal)
119 {
120     Color fillColor = context.fillColor();
121     ColorSpace fillColorSpace = context.fillColorSpace();
122     bool opaque = !fillColor.hasAlpha();
123     bool lastShadowIterationShouldDrawText = !stroked && opaque;
124     if (!opaque)
125         context.setFillColor(Color::black, fillColorSpace);
126
127     do {
128         ShadowApplier shadowApplier(context, shadow, boxRect, lastShadowIterationShouldDrawText, opaque, horizontal ? Horizontal : Vertical);
129         if (shadowApplier.nothingToDraw()) {
130             shadow = shadow->next();
131             continue;
132         }
133
134         IntSize extraOffset = roundedIntSize(shadowApplier.extraOffset());
135         if (!shadow && !opaque)
136             context.setFillColor(fillColor, fillColorSpace);
137
138         if (startOffset <= endOffset)
139             drawTextOrEmphasisMarks(context, font, textRun, emphasisMark, emphasisMarkOffset, textOrigin + extraOffset, startOffset, endOffset);
140         else {
141             if (endOffset > 0)
142                 drawTextOrEmphasisMarks(context, font, textRun, emphasisMark, emphasisMarkOffset, textOrigin + extraOffset, 0, endOffset);
143             if (startOffset < truncationPoint)
144                 drawTextOrEmphasisMarks(context, font, textRun, emphasisMark, emphasisMarkOffset, textOrigin + extraOffset, startOffset, truncationPoint);
145         }
146
147         if (!shadow)
148             break;
149
150         shadow = shadow->next();
151     } while (shadow || !lastShadowIterationShouldDrawText);
152 }
153
154 void TextPainter::paintText()
155 {
156     FloatPoint boxOrigin = m_boxRect.location();
157
158     if (!m_paintSelectedTextOnly) {
159         // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side
160         // effect, so only when we know we're stroking, do a save/restore.
161         GraphicsContextStateSaver stateSaver(m_context, m_textPaintStyle.strokeWidth > 0);
162
163         updateGraphicsContext(m_context, m_textPaintStyle);
164         if (!m_paintSelectedTextSeparately || m_endPositionInTextRun <= m_startPositionInTextRun) {
165             // FIXME: Truncate right-to-left text correctly.
166             paintTextWithShadows(m_context, m_font, m_textRun, nullAtom, 0, 0, m_length, m_length, m_textOrigin, m_boxRect, m_textShadow, m_textPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
167         } else
168             paintTextWithShadows(m_context, m_font, m_textRun, nullAtom, 0, m_endPositionInTextRun, m_startPositionInTextRun, m_length, m_textOrigin, m_boxRect, m_textShadow, m_textPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
169
170         if (!m_emphasisMark.isEmpty()) {
171             updateGraphicsContext(m_context, m_textPaintStyle, UseEmphasisMarkColor);
172
173             static NeverDestroyed<TextRun> objectReplacementCharacterTextRun(&objectReplacementCharacter, 1);
174             TextRun& emphasisMarkTextRun = m_combinedText ? objectReplacementCharacterTextRun.get() : m_textRun;
175             FloatPoint emphasisMarkTextOrigin = m_combinedText ? FloatPoint(boxOrigin.x() + m_boxRect.width() / 2, boxOrigin.y() + m_font.fontMetrics().ascent()) : m_textOrigin;
176             if (m_combinedText)
177                 m_context.concatCTM(rotation(m_boxRect, Clockwise));
178
179             if (!m_paintSelectedTextSeparately || m_endPositionInTextRun <= m_startPositionInTextRun) {
180                 // FIXME: Truncate right-to-left text correctly.
181                 paintTextWithShadows(m_context, m_combinedText ? m_combinedText->originalFont() : m_font, emphasisMarkTextRun, m_emphasisMark, m_emphasisMarkOffset, 0, m_length, m_length, emphasisMarkTextOrigin, m_boxRect, m_textShadow, m_textPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
182             } else
183                 paintTextWithShadows(m_context, m_combinedText ? m_combinedText->originalFont() : m_font, emphasisMarkTextRun, m_emphasisMark, m_emphasisMarkOffset, m_endPositionInTextRun, m_startPositionInTextRun, m_length, emphasisMarkTextOrigin, m_boxRect, m_textShadow, m_textPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
184
185             if (m_combinedText)
186                 m_context.concatCTM(rotation(m_boxRect, Counterclockwise));
187         }
188     }
189
190     if ((m_paintSelectedTextOnly || m_paintSelectedTextSeparately) && m_startPositionInTextRun < m_endPositionInTextRun) {
191         // paint only the text that is selected
192         GraphicsContextStateSaver stateSaver(m_context, m_selectionPaintStyle.strokeWidth > 0);
193
194         updateGraphicsContext(m_context, m_selectionPaintStyle);
195         paintTextWithShadows(m_context, m_font, m_textRun, nullAtom, 0, m_startPositionInTextRun, m_endPositionInTextRun, m_length, m_textOrigin, m_boxRect, m_selectionShadow, m_selectionPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
196         if (!m_emphasisMark.isEmpty()) {
197             updateGraphicsContext(m_context, m_selectionPaintStyle, UseEmphasisMarkColor);
198
199             DEPRECATED_DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
200             TextRun& emphasisMarkTextRun = m_combinedText ? objectReplacementCharacterTextRun : m_textRun;
201             FloatPoint emphasisMarkTextOrigin = m_combinedText ? FloatPoint(boxOrigin.x() + m_boxRect.width() / 2, boxOrigin.y() + m_font.fontMetrics().ascent()) : m_textOrigin;
202             if (m_combinedText)
203                 m_context.concatCTM(rotation(m_boxRect, Clockwise));
204
205             paintTextWithShadows(m_context, m_combinedText ? m_combinedText->originalFont() : m_font, emphasisMarkTextRun, m_emphasisMark, m_emphasisMarkOffset, m_startPositionInTextRun, m_endPositionInTextRun, m_length, emphasisMarkTextOrigin, m_boxRect, m_selectionShadow, m_selectionPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
206
207             if (m_combinedText)
208                 m_context.concatCTM(rotation(m_boxRect, Counterclockwise));
209         }
210     }
211 }
212
213 #if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
214 DashArray TextPainter::dashesForIntersectionsWithRect(const FloatRect& lineExtents)
215 {
216     return m_font.dashesForIntersectionsWithRect(m_textRun, m_textOrigin, lineExtents);
217 }
218 #endif
219
220 } // namespace WebCore