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