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