a3727e986c66237db1647f10a79bb758804e4d22
[WebKit-https.git] / Source / WebCore / rendering / TextAutoSizing.cpp
1 /*
2  * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "TextAutoSizing.h"
28
29 #if ENABLE(IOS_TEXT_AUTOSIZING)
30
31 #include "CSSFontSelector.h"
32 #include "Document.h"
33 #include "RenderListMarker.h"
34 #include "RenderText.h"
35 #include "StyleResolver.h"
36
37 namespace WebCore {
38
39 static PassRefPtr<RenderStyle> cloneRenderStyleWithState(const RenderStyle& currentStyle)
40 {
41     RefPtr<RenderStyle> newStyle = RenderStyle::clone(&currentStyle);
42     if (currentStyle.lastChildState())
43         newStyle->setLastChildState();
44     if (currentStyle.firstChildState())
45         newStyle->setFirstChildState();
46     return newStyle.release();
47 }
48
49 TextAutoSizingKey::TextAutoSizingKey()
50     : m_style(0)
51     , m_doc(0)
52 {
53 }
54
55 TextAutoSizingKey::TextAutoSizingKey(RenderStyle* style, Document* doc)
56     : m_style(style)
57     , m_doc(doc)
58 {
59     ref();
60 }
61
62 TextAutoSizingKey::TextAutoSizingKey(const TextAutoSizingKey& other)
63     : m_style(other.m_style)
64     , m_doc(other.m_doc)
65 {
66     ref();
67 }
68
69 TextAutoSizingKey::~TextAutoSizingKey()
70 {
71     deref();
72 }
73
74 TextAutoSizingKey& TextAutoSizingKey::operator=(const TextAutoSizingKey& other)
75 {
76     other.ref();
77     deref();
78     m_style = other.m_style;
79     m_doc = other.m_doc;
80     return *this;
81 }
82
83 void TextAutoSizingKey::ref() const
84 {
85     if (isValidStyle())
86         m_style->ref();
87 }
88
89 void TextAutoSizingKey::deref() const
90 {
91     if (isValidStyle() && isValidDoc())
92         m_style->deref();
93 }
94
95 int TextAutoSizingValue::numNodes() const
96 {
97     return m_autoSizedNodes.size();
98 }
99
100 void TextAutoSizingValue::addNode(Node* node, float size)
101 {
102     ASSERT(node);
103     downcast<RenderText>(*node->renderer()).setCandidateComputedTextSize(size);
104     m_autoSizedNodes.add(node);
105 }
106
107 #define MAX_SCALE_INCREASE 1.7f
108
109 bool TextAutoSizingValue::adjustNodeSizes()
110 {
111     bool objectsRemoved = false;
112     
113     // Remove stale nodes.  Nodes may have had their renderers detached.  We'll
114     // also need to remove the style from the documents m_textAutoSizedNodes
115     // collection.  Return true indicates we need to do that removal.
116     Vector<RefPtr<Node> > nodesForRemoval;
117     HashSet<RefPtr<Node> >::iterator end = m_autoSizedNodes.end();
118     for (HashSet<RefPtr<Node> >::iterator i = m_autoSizedNodes.begin(); i != end; ++i) {
119         RefPtr<Node> autoSizingNode = *i;
120         RenderText* text = static_cast<RenderText*>(autoSizingNode->renderer());
121         if (!text || !text->style().textSizeAdjust().isAuto() || !text->candidateComputedTextSize()) {
122             // remove node.
123             nodesForRemoval.append(autoSizingNode);
124             objectsRemoved = true;
125         }
126     }
127     
128     unsigned count = nodesForRemoval.size();
129     for (unsigned i = 0; i < count; i++)
130         m_autoSizedNodes.remove(nodesForRemoval[i]);
131     
132     // If we only have one piece of text with the style on the page don't
133     // adjust it's size.
134     if (m_autoSizedNodes.size() <= 1)
135         return objectsRemoved;
136     
137     // Compute average size
138     float cumulativeSize = 0;
139     end = m_autoSizedNodes.end();
140     for (HashSet<RefPtr<Node> >::iterator i = m_autoSizedNodes.begin(); i != end; ++i) {
141         RefPtr<Node> autoSizingNode = *i;
142         RenderText* renderText = static_cast<RenderText*>(autoSizingNode->renderer());
143         cumulativeSize += renderText->candidateComputedTextSize();
144     }
145     
146     float averageSize = roundf(cumulativeSize / m_autoSizedNodes.size());
147     
148     // Adjust sizes
149     bool firstPass = true;
150     end = m_autoSizedNodes.end();
151     for (HashSet<RefPtr<Node> >::iterator i = m_autoSizedNodes.begin(); i != end; ++i) {
152         const RefPtr<Node>& autoSizingNode = *i;
153         RenderText* text = static_cast<RenderText*>(autoSizingNode->renderer());
154         if (text && text->style().fontDescription().computedSize() != averageSize) {
155             float specifiedSize = text->style().fontDescription().specifiedSize();
156             float scaleChange = averageSize / specifiedSize;
157             if (scaleChange > MAX_SCALE_INCREASE && firstPass) {
158                 firstPass = false;
159                 averageSize = roundf(specifiedSize * MAX_SCALE_INCREASE);
160                 scaleChange = averageSize / specifiedSize;
161             }
162             
163             RefPtr<RenderStyle> style = cloneRenderStyleWithState(text->style());
164             FontDescription fontDescription = style->fontDescription();
165             fontDescription.setComputedSize(averageSize);
166             style->setFontDescription(fontDescription);
167             style->font().update(autoSizingNode->document().ensureStyleResolver().fontSelector());
168             text->parent()->setStyle(style.releaseNonNull());
169             
170             RenderElement* parentRenderer = text->parent();
171             if (parentRenderer->isAnonymousBlock())
172                 parentRenderer = parentRenderer->parent();
173             
174             // If we have a list we should resize ListMarkers separately.
175             RenderObject* listMarkerRenderer = parentRenderer->firstChild();
176             if (listMarkerRenderer->isListMarker()) {
177                 RefPtr<RenderStyle> style = cloneRenderStyleWithState(listMarkerRenderer->style());
178                 style->setFontDescription(fontDescription);
179                 style->font().update(autoSizingNode->document().ensureStyleResolver().fontSelector());
180                 toRenderListMarker(*listMarkerRenderer).setStyle(style.releaseNonNull());
181             }
182             
183             // Resize the line height of the parent.
184             const RenderStyle& parentStyle = parentRenderer->style();
185             Length lineHeightLength = parentStyle.specifiedLineHeight();
186             
187             int specifiedLineHeight = 0;
188             if (lineHeightLength.isPercent())
189                 specifiedLineHeight = minimumValueForLength(lineHeightLength, fontDescription.specifiedSize());
190             else
191                 specifiedLineHeight = lineHeightLength.value();
192             
193             int lineHeight = specifiedLineHeight * scaleChange;
194             if (!lineHeightLength.isFixed() || lineHeightLength.value() != lineHeight) {
195                 RefPtr<RenderStyle> newParentStyle = cloneRenderStyleWithState(parentStyle);
196                 newParentStyle->setLineHeight(Length(lineHeight, Fixed));
197                 newParentStyle->setSpecifiedLineHeight(lineHeightLength);
198                 newParentStyle->setFontDescription(fontDescription);
199                 newParentStyle->font().update(autoSizingNode->document().ensureStyleResolver().fontSelector());
200                 parentRenderer->setStyle(newParentStyle.releaseNonNull());
201             }
202         }
203     }
204     
205     return objectsRemoved;
206 }
207
208 void TextAutoSizingValue::reset()
209 {
210     HashSet<RefPtr<Node> >::iterator end = m_autoSizedNodes.end();
211     for (HashSet<RefPtr<Node> >::iterator i = m_autoSizedNodes.begin(); i != end; ++i) {
212         const RefPtr<Node>& autoSizingNode = *i;
213         RenderText* text = static_cast<RenderText*>(autoSizingNode->renderer());
214         if (!text)
215             continue;
216         // Reset the font size back to the original specified size
217         FontDescription fontDescription = text->style().fontDescription();
218         float originalSize = fontDescription.specifiedSize();
219         if (fontDescription.computedSize() != originalSize) {
220             fontDescription.setComputedSize(originalSize);
221             RefPtr<RenderStyle> style = cloneRenderStyleWithState(text->style());
222             style->setFontDescription(fontDescription);
223             style->font().update(autoSizingNode->document().ensureStyleResolver().fontSelector());
224             text->parent()->setStyle(style.releaseNonNull());
225         }
226         // Reset the line height of the parent.
227         RenderElement* parentRenderer = text->parent();
228         if (!parentRenderer)
229             continue;
230         
231         if (parentRenderer->isAnonymousBlock())
232             parentRenderer = parentRenderer->parent();
233         
234         const RenderStyle& parentStyle = parentRenderer->style();
235         Length originalLineHeight = parentStyle.specifiedLineHeight();
236         if (originalLineHeight != parentStyle.lineHeight()) {
237             RefPtr<RenderStyle> newParentStyle = cloneRenderStyleWithState(parentStyle);
238             newParentStyle->setLineHeight(originalLineHeight);
239             newParentStyle->setFontDescription(fontDescription);
240             newParentStyle->font().update(autoSizingNode->document().ensureStyleResolver().fontSelector());
241             parentRenderer->setStyle(newParentStyle.releaseNonNull());
242         }
243     }
244 }
245
246 } // namespace WebCore
247
248 #endif // ENABLE(IOS_TEXT_AUTOSIZING)