Migrate from ints to unsigneds when referring to indices into strings
[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.hasAlpha();
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     // FIXME: Truncate right-to-left text correctly.
127     paintTextWithShadows(shadow, *m_font, textRun, boxRect, textOrigin, startOffset, endOffset, nullAtom, 0, paintStyle.strokeWidth > 0);
128
129     if (m_emphasisMark.isEmpty())
130         return;
131
132     FloatPoint boxOrigin = boxRect.location();
133     updateGraphicsContext(m_context, paintStyle, UseEmphasisMarkColor);
134     static NeverDestroyed<TextRun> objectReplacementCharacterTextRun(StringView(&objectReplacementCharacter, 1));
135     const TextRun& emphasisMarkTextRun = m_combinedText ? objectReplacementCharacterTextRun.get() : textRun;
136     FloatPoint emphasisMarkTextOrigin = m_combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + m_font->fontMetrics().ascent()) : textOrigin;
137     if (m_combinedText)
138         m_context.concatCTM(rotation(boxRect, Clockwise));
139
140     // FIXME: Truncate right-to-left text correctly.
141     paintTextWithShadows(shadow, m_combinedText ? m_combinedText->originalFont() : *m_font, emphasisMarkTextRun, boxRect, emphasisMarkTextOrigin, startOffset, endOffset,
142         m_emphasisMark, m_emphasisMarkOffset, paintStyle.strokeWidth > 0);
143
144     if (m_combinedText)
145         m_context.concatCTM(rotation(boxRect, Counterclockwise));
146 }
147     
148 void TextPainter::paintText(const TextRun& textRun, unsigned length, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned selectionStart, unsigned selectionEnd,
149     bool paintSelectedTextOnly, bool paintSelectedTextSeparately)
150 {
151     ASSERT(m_font);
152     if (!paintSelectedTextOnly) {
153         // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side
154         // effect, so only when we know we're stroking, do a save/restore.
155         GraphicsContextStateSaver stateSaver(m_context, m_textPaintStyle.strokeWidth > 0);
156         updateGraphicsContext(m_context, m_textPaintStyle);
157         bool fullPaint = !paintSelectedTextSeparately || selectionEnd <= selectionStart;
158         if (fullPaint)
159             paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, 0, length, m_textPaintStyle, m_textShadow);
160         else {
161             // Paint the before and after selection parts.
162             if (selectionStart > 0)
163                 paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, 0, selectionStart, m_textPaintStyle, m_textShadow);
164             if (selectionEnd < length)
165                 paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, selectionEnd, length, m_textPaintStyle, m_textShadow);
166         }
167     }
168     // Paint only the text that is selected.
169     if ((paintSelectedTextOnly || paintSelectedTextSeparately) && selectionStart < selectionEnd) {
170         GraphicsContextStateSaver stateSaver(m_context, m_selectionPaintStyle.strokeWidth > 0);
171         updateGraphicsContext(m_context, m_selectionPaintStyle);
172         paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, selectionStart, selectionEnd, m_selectionPaintStyle, m_selectionShadow);
173     }
174 }
175
176 } // namespace WebCore