dcaa516fb4c96475aea7a7f1065cb1727051eb22
[WebKit-https.git] / Source / WebCore / rendering / TextPainter.cpp
1 /*
2  * Copyright (C) 2013 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "TextPainter.h"
31
32 #include "GraphicsContext.h"
33 #include "InlineTextBox.h"
34 #include "RenderCombineText.h"
35 #include "TextPaintStyle.h"
36
37 namespace WebCore {
38
39 TextPainter::TextPainter(GraphicsContext& context, bool paintSelectedTextOnly, bool paintSelectedTextSeparately, const Font& font,
40     int startPositionInTextRun, int endPositionInTextBoxString, int length, const AtomicString& emphasisMark, RenderCombineText* combinedText, TextRun& textRun,
41     FloatRect& boxRect, FloatPoint& textOrigin, int emphasisMarkOffset, const ShadowData* textShadow, const ShadowData* selectionShadow,
42     bool textBoxIsHorizontal, TextPaintStyle& textPaintStyle, TextPaintStyle& selectionPaintStyle)
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     , m_savedDrawingStateForMask(&context, &textPaintStyle, &selectionPaintStyle, textShadow, selectionShadow)
57 {
58 }
59
60 static void drawTextOrEmphasisMarks(GraphicsContext& context, const Font& font, const TextRun& textRun, const AtomicString& emphasisMark,
61     int emphasisMarkOffset, const FloatPoint& point, const int from, const int to)
62 {
63     if (emphasisMark.isEmpty())
64         context.drawText(font, textRun, point, from, to);
65     else
66         context.drawEmphasisMarks(font, textRun, emphasisMark, point + IntSize(0, emphasisMarkOffset), from, to);
67 }
68
69 static void paintTextWithShadows(GraphicsContext* context, const Font& font, const TextRun& textRun, const AtomicString& emphasisMark,
70     int emphasisMarkOffset, int startOffset, int endOffset, int truncationPoint, const FloatPoint& textOrigin, const FloatRect& boxRect,
71     const ShadowData* shadow, bool stroked, bool horizontal)
72 {
73     Color fillColor = context->fillColor();
74     ColorSpace fillColorSpace = context->fillColorSpace();
75     bool opaque = !fillColor.hasAlpha();
76     if (!opaque)
77         context->setFillColor(Color::black, fillColorSpace);
78
79     do {
80         IntSize extraOffset;
81         if (shadow)
82             extraOffset = roundedIntSize(InlineTextBox::applyShadowToGraphicsContext(context, shadow, boxRect, stroked, opaque, horizontal));
83         else if (!opaque)
84             context->setFillColor(fillColor, fillColorSpace);
85
86         if (startOffset <= endOffset)
87             drawTextOrEmphasisMarks(*context, font, textRun, emphasisMark, emphasisMarkOffset, textOrigin + extraOffset, startOffset, endOffset);
88         else {
89             if (endOffset > 0)
90                 drawTextOrEmphasisMarks(*context, font, textRun, emphasisMark, emphasisMarkOffset, textOrigin + extraOffset, 0, endOffset);
91             if (startOffset < truncationPoint)
92                 drawTextOrEmphasisMarks(*context, font, textRun, emphasisMark, emphasisMarkOffset, textOrigin + extraOffset, startOffset, truncationPoint);
93         }
94
95         if (!shadow)
96             break;
97
98         if (shadow->next() || stroked || !opaque)
99             context->restore();
100         else
101             context->clearShadow();
102
103         shadow = shadow->next();
104     } while (shadow || stroked || !opaque);
105 }
106
107 void TextPainter::paintText()
108 {
109     ASSERT(m_savedDrawingStateForMask.m_textPaintStyle);
110     ASSERT(m_savedDrawingStateForMask.m_selectionPaintStyle);
111     
112     FloatPoint boxOrigin = boxRect().location();
113
114     if (!m_paintSelectedTextOnly) {
115         // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side
116         // effect, so only when we know we're stroking, do a save/restore.
117         GraphicsContextStateSaver stateSaver(*m_savedDrawingStateForMask.m_context, m_savedDrawingStateForMask.m_textPaintStyle->strokeWidth > 0);
118
119         updateGraphicsContext(*m_savedDrawingStateForMask.m_context, *m_savedDrawingStateForMask.m_textPaintStyle);
120         if (!m_paintSelectedTextSeparately || m_endPositionInTextRun <= m_startPositionInTextRun) {
121             // FIXME: Truncate right-to-left text correctly.
122             paintTextWithShadows(m_savedDrawingStateForMask.m_context, m_font, m_textRun, nullAtom, 0, 0, m_length, m_length, m_textOrigin, m_boxRect, m_savedDrawingStateForMask.m_textShadow, m_savedDrawingStateForMask.m_textPaintStyle->strokeWidth > 0, m_textBoxIsHorizontal);
123         } else
124             paintTextWithShadows(m_savedDrawingStateForMask.m_context, m_font, m_textRun, nullAtom, 0, m_endPositionInTextRun, m_startPositionInTextRun, m_length, m_textOrigin, m_boxRect, m_savedDrawingStateForMask.m_textShadow, m_savedDrawingStateForMask.m_textPaintStyle->strokeWidth > 0, m_textBoxIsHorizontal);
125
126         if (!m_emphasisMark.isEmpty()) {
127             updateGraphicsContext(*m_savedDrawingStateForMask.m_context, *m_savedDrawingStateForMask.m_textPaintStyle, UseEmphasisMarkColor);
128
129             DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
130             TextRun& emphasisMarkTextRun = m_combinedText ? objectReplacementCharacterTextRun : m_textRun;
131             FloatPoint emphasisMarkTextOrigin = m_combinedText ? FloatPoint(boxOrigin.x() + m_boxRect.width() / 2, boxOrigin.y() + m_font.fontMetrics().ascent()) : m_textOrigin;
132             if (m_combinedText)
133                 m_savedDrawingStateForMask.m_context->concatCTM(rotation(m_boxRect, Clockwise));
134
135             if (!m_paintSelectedTextSeparately || m_endPositionInTextRun <= m_startPositionInTextRun) {
136                 // FIXME: Truncate right-to-left text correctly.
137                 paintTextWithShadows(m_savedDrawingStateForMask.m_context, m_combinedText ? m_combinedText->originalFont() : m_font, emphasisMarkTextRun, m_emphasisMark, m_emphasisMarkOffset, 0, m_length, m_length, emphasisMarkTextOrigin, m_boxRect, m_savedDrawingStateForMask.m_textShadow, m_savedDrawingStateForMask.m_textPaintStyle->strokeWidth > 0, m_textBoxIsHorizontal);
138             } else
139                 paintTextWithShadows(m_savedDrawingStateForMask.m_context, m_combinedText ? m_combinedText->originalFont() : m_font, emphasisMarkTextRun, m_emphasisMark, m_emphasisMarkOffset, m_endPositionInTextRun, m_startPositionInTextRun, m_length, emphasisMarkTextOrigin, m_boxRect, m_savedDrawingStateForMask.m_textShadow, m_savedDrawingStateForMask.m_textPaintStyle->strokeWidth > 0, m_textBoxIsHorizontal);
140
141             if (m_combinedText)
142                 m_savedDrawingStateForMask.m_context->concatCTM(rotation(m_boxRect, Counterclockwise));
143         }
144     }
145
146     if ((m_paintSelectedTextOnly || m_paintSelectedTextSeparately) && m_startPositionInTextRun < m_endPositionInTextRun) {
147         // paint only the text that is selected
148         GraphicsContextStateSaver stateSaver(*m_savedDrawingStateForMask.m_context, m_savedDrawingStateForMask.m_selectionPaintStyle->strokeWidth > 0);
149
150         updateGraphicsContext(*m_savedDrawingStateForMask.m_context, *m_savedDrawingStateForMask.m_selectionPaintStyle);
151         paintTextWithShadows(m_savedDrawingStateForMask.m_context, m_font, m_textRun, nullAtom, 0, m_startPositionInTextRun, m_endPositionInTextRun, m_length, m_textOrigin, m_boxRect, m_savedDrawingStateForMask.m_selectionShadow, m_savedDrawingStateForMask.m_selectionPaintStyle->strokeWidth > 0, m_textBoxIsHorizontal);
152         if (!m_emphasisMark.isEmpty()) {
153             updateGraphicsContext(*m_savedDrawingStateForMask.m_context, *m_savedDrawingStateForMask.m_selectionPaintStyle, UseEmphasisMarkColor);
154
155             DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1));
156             TextRun& emphasisMarkTextRun = m_combinedText ? objectReplacementCharacterTextRun : m_textRun;
157             FloatPoint emphasisMarkTextOrigin = m_combinedText ? FloatPoint(boxOrigin.x() + m_boxRect.width() / 2, boxOrigin.y() + m_font.fontMetrics().ascent()) : m_textOrigin;
158             if (m_combinedText)
159                 m_savedDrawingStateForMask.m_context->concatCTM(rotation(m_boxRect, Clockwise));
160
161             paintTextWithShadows(m_savedDrawingStateForMask.m_context, m_combinedText ? m_combinedText->originalFont() : m_font, emphasisMarkTextRun, m_emphasisMark, m_emphasisMarkOffset, m_startPositionInTextRun, m_endPositionInTextRun, m_length, emphasisMarkTextOrigin, m_boxRect, m_savedDrawingStateForMask.m_selectionShadow, m_savedDrawingStateForMask.m_selectionPaintStyle->strokeWidth > 0, m_textBoxIsHorizontal);
162
163             if (m_combinedText)
164                 m_savedDrawingStateForMask.m_context->concatCTM(rotation(m_boxRect, Counterclockwise));
165         }
166     }
167 }
168
169 void TextPainter::paintTextInContext(GraphicsContext& context, float amountToIncreaseStrokeWidthBy)
170 {
171     SavedDrawingStateForMask savedDrawingStateForMask = m_savedDrawingStateForMask;
172     
173     ASSERT(m_savedDrawingStateForMask.m_textPaintStyle);
174     ASSERT(m_savedDrawingStateForMask.m_selectionPaintStyle);
175     m_savedDrawingStateForMask.m_context = &context;
176     m_savedDrawingStateForMask.m_textPaintStyle->strokeWidth += amountToIncreaseStrokeWidthBy;
177     m_savedDrawingStateForMask.m_selectionPaintStyle->strokeWidth += amountToIncreaseStrokeWidthBy;
178     m_savedDrawingStateForMask.m_textShadow = nullptr;
179     m_savedDrawingStateForMask.m_selectionShadow = nullptr;
180     paintText();
181
182     m_savedDrawingStateForMask = savedDrawingStateForMask;
183 }
184
185 } // namespace WebCore