Factor text autosizing into a class
[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(TEXT_AUTOSIZING)
30
31 #include "CSSFontSelector.h"
32 #include "Document.h"
33 #include "FontCascade.h"
34 #include "Logging.h"
35 #include "RenderBlock.h"
36 #include "RenderListMarker.h"
37 #include "RenderText.h"
38 #include "RenderTextFragment.h"
39 #include "StyleResolver.h"
40
41 namespace WebCore {
42
43 static RenderStyle cloneRenderStyleWithState(const RenderStyle& currentStyle)
44 {
45     auto newStyle = RenderStyle::clone(currentStyle);
46     if (currentStyle.lastChildState())
47         newStyle.setLastChildState();
48     if (currentStyle.firstChildState())
49         newStyle.setFirstChildState();
50     return newStyle;
51 }
52
53 TextAutoSizingKey::TextAutoSizingKey(DeletedTag)
54 {
55     HashTraits<std::unique_ptr<RenderStyle>>::constructDeletedValue(m_style);
56 }
57
58 TextAutoSizingKey::TextAutoSizingKey(const RenderStyle& style, unsigned hash)
59     : m_style(RenderStyle::clonePtr(style)) // FIXME: This seems very inefficient.
60     , m_hash(hash)
61 {
62 }
63
64 void TextAutoSizingValue::addTextNode(Text& node, float size)
65 {
66     node.renderer()->setCandidateComputedTextSize(size);
67     m_autoSizedNodes.add(&node);
68 }
69
70 static const float maxScaleIncrease = 1.7f;
71
72 auto TextAutoSizingValue::adjustTextNodeSizes() -> StillHasNodes
73 {
74     // Remove stale nodes. Nodes may have had their renderers detached. We'll also need to remove the style from the documents m_textAutoSizedNodes
75     // collection. Return true indicates we need to do that removal.
76     Vector<Text*> nodesForRemoval;
77     for (auto& textNode : m_autoSizedNodes) {
78         auto* renderer = textNode->renderer();
79         if (!renderer || !renderer->style().textSizeAdjust().isAuto() || !renderer->candidateComputedTextSize())
80             nodesForRemoval.append(textNode.get());
81     }
82
83     for (auto& node : nodesForRemoval)
84         m_autoSizedNodes.remove(node);
85
86     StillHasNodes stillHasNodes = m_autoSizedNodes.isEmpty() ? StillHasNodes::No : StillHasNodes::Yes;
87
88     // If we only have one piece of text with the style on the page don't adjust it's size.
89     if (m_autoSizedNodes.size() <= 1)
90         return stillHasNodes;
91
92     // Compute average size.
93     float cumulativeSize = 0;
94     for (auto& node : m_autoSizedNodes)
95         cumulativeSize += node->renderer()->candidateComputedTextSize();
96
97     float averageSize = std::round(cumulativeSize / m_autoSizedNodes.size());
98
99     // Adjust sizes.
100     bool firstPass = true;
101     for (auto& node : m_autoSizedNodes) {
102         auto& renderer = *node->renderer();
103         if (renderer.style().fontDescription().computedSize() == averageSize)
104             continue;
105
106         float specifiedSize = renderer.style().fontDescription().specifiedSize();
107         float scaleChange = averageSize / specifiedSize;
108         if (scaleChange > maxScaleIncrease && firstPass) {
109             firstPass = false;
110             averageSize = std::round(specifiedSize * maxScaleIncrease);
111             scaleChange = averageSize / specifiedSize;
112         }
113
114         LOG(TextAutosizing, "  adjust node size %p firstPass=%d averageSize=%f scaleChange=%f", node.get(), firstPass, averageSize, scaleChange);
115
116         auto* parentRenderer = renderer.parent();
117
118         auto style = cloneRenderStyleWithState(renderer.style());
119         auto fontDescription = style.fontDescription();
120         fontDescription.setComputedSize(averageSize);
121         style.setFontDescription(fontDescription);
122         style.fontCascade().update(&node->document().fontSelector());
123         parentRenderer->setStyle(WTFMove(style));
124
125         if (parentRenderer->isAnonymousBlock())
126             parentRenderer = parentRenderer->parent();
127
128         // If we have a list we should resize ListMarkers separately.
129         if (is<RenderListMarker>(*parentRenderer->firstChild())) {
130             auto& listMarkerRenderer = downcast<RenderListMarker>(*parentRenderer->firstChild());
131             auto style = cloneRenderStyleWithState(listMarkerRenderer.style());
132             style.setFontDescription(fontDescription);
133             style.fontCascade().update(&node->document().fontSelector());
134             listMarkerRenderer.setStyle(WTFMove(style));
135         }
136
137         // Resize the line height of the parent.
138         auto& parentStyle = parentRenderer->style();
139         auto& lineHeightLength = parentStyle.specifiedLineHeight();
140
141         int specifiedLineHeight;
142         if (lineHeightLength.isPercent())
143             specifiedLineHeight = minimumValueForLength(lineHeightLength, fontDescription.specifiedSize());
144         else
145             specifiedLineHeight = lineHeightLength.value();
146
147         // This calculation matches the line-height computed size calculation in StyleBuilderCustom::applyValueLineHeight().
148         int lineHeight = specifiedLineHeight * scaleChange;
149         if (lineHeightLength.isFixed() && lineHeightLength.value() == lineHeight)
150             continue;
151
152         auto newParentStyle = cloneRenderStyleWithState(parentStyle);
153         newParentStyle.setLineHeight(Length(lineHeight, Fixed));
154         newParentStyle.setSpecifiedLineHeight(Length { lineHeightLength });
155         newParentStyle.setFontDescription(fontDescription);
156         newParentStyle.fontCascade().update(&node->document().fontSelector());
157         parentRenderer->setStyle(WTFMove(newParentStyle));
158     }
159
160     // FIXME: All render tree mutations should be done via RenderTreeUpdater.
161     for (auto& node : m_autoSizedNodes) {
162         auto& textRenderer = *node->renderer();
163         if (!is<RenderTextFragment>(textRenderer))
164             continue;
165         auto* block = downcast<RenderTextFragment>(textRenderer).blockForAccompanyingFirstLetter();
166         if (!block)
167             continue;
168         block->updateFirstLetter();
169     }
170
171     return stillHasNodes;
172 }
173
174 TextAutoSizingValue::~TextAutoSizingValue()
175 {
176     reset();
177 }
178
179 void TextAutoSizingValue::reset()
180 {
181     for (auto& node : m_autoSizedNodes) {
182         auto* renderer = node->renderer();
183         if (!renderer)
184             continue;
185
186         auto* parentRenderer = renderer->parent();
187         if (!parentRenderer)
188             continue;
189
190         // Reset the font size back to the original specified size
191         auto fontDescription = renderer->style().fontDescription();
192         float originalSize = fontDescription.specifiedSize();
193         if (fontDescription.computedSize() != originalSize) {
194             fontDescription.setComputedSize(originalSize);
195             auto style = cloneRenderStyleWithState(renderer->style());
196             style.setFontDescription(fontDescription);
197             style.fontCascade().update(&node->document().fontSelector());
198             parentRenderer->setStyle(WTFMove(style));
199         }
200
201         // Reset the line height of the parent.
202         if (parentRenderer->isAnonymousBlock())
203             parentRenderer = parentRenderer->parent();
204
205         auto& parentStyle = parentRenderer->style();
206         auto& originalLineHeight = parentStyle.specifiedLineHeight();
207         if (originalLineHeight == parentStyle.lineHeight())
208             continue;
209
210         auto newParentStyle = cloneRenderStyleWithState(parentStyle);
211         newParentStyle.setLineHeight(Length { originalLineHeight });
212         newParentStyle.setFontDescription(fontDescription);
213         newParentStyle.fontCascade().update(&node->document().fontSelector());
214         parentRenderer->setStyle(WTFMove(newParentStyle));
215     }
216 }
217
218 void TextAutoSizing::addTextNode(Text& node, float candidateSize)
219 {
220     LOG(TextAutosizing, " addAutoSizedNode %p candidateSize=%f", &node, candidateSize);
221     auto addResult = m_textNodes.add<TextAutoSizingHashTranslator>(node.renderer()->style(), nullptr);
222     if (addResult.isNewEntry)
223         addResult.iterator->value = std::make_unique<TextAutoSizingValue>();
224     addResult.iterator->value->addTextNode(node, candidateSize);
225 }
226
227 void TextAutoSizing::updateRenderTree()
228 {
229     m_textNodes.removeIf([](auto& keyAndValue) {
230         return keyAndValue.value->adjustTextNodeSizes() == TextAutoSizingValue::StillHasNodes::No;
231     });
232 }
233
234 void TextAutoSizing::reset()
235 {
236     m_textNodes.clear();
237 }
238
239 } // namespace WebCore
240
241 #endif // ENABLE(TEXT_AUTOSIZING)