[Text Autosizing] Cleanup change: converter the pointer argument to be a reference...
[WebKit-https.git] / Source / WebCore / rendering / TextAutosizer.cpp
1 /*
2  * Copyright (C) 2012 Google Inc. All rights reserved.
3  * Copyright (C) 2012 Apple Inc. All rights reserved.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #include "config.h"
22
23 #if ENABLE(TEXT_AUTOSIZING)
24
25 #include "TextAutosizer.h"
26
27 #include "Document.h"
28 #include "HTMLElement.h"
29 #include "InspectorInstrumentation.h"
30 #include "IntSize.h"
31 #include "RenderObject.h"
32 #include "RenderStyle.h"
33 #include "RenderText.h"
34 #include "RenderView.h"
35 #include "Settings.h"
36 #include "StyleInheritedData.h"
37
38 #include <algorithm>
39 #include <wtf/StdLibExtras.h>
40
41 namespace WebCore {
42
43 using namespace HTMLNames;
44
45 struct TextAutosizingWindowInfo {
46     IntSize windowSize;
47     IntSize minLayoutSize;
48 };
49
50 struct TextAutosizingClusterInfo {
51     explicit TextAutosizingClusterInfo(RenderBlock* root)
52         : root(root)
53         , blockContainingAllText(0)
54         , maxAllowedDifferenceFromTextWidth(150)
55     {
56     }
57
58     RenderBlock* root;
59     const RenderBlock* blockContainingAllText;
60
61     // Upper limit on the difference between the width of the cluster's block containing all
62     // text and that of a narrow child before the child becomes a separate cluster.
63     float maxAllowedDifferenceFromTextWidth;
64 };
65
66
67 static const Vector<QualifiedName>& formInputTags()
68 {
69     // Returns the tags for the form input elements.
70     DEFINE_STATIC_LOCAL(Vector<QualifiedName>, formInputTags, ());
71     if (formInputTags.isEmpty()) {
72         formInputTags.append(inputTag);
73         formInputTags.append(buttonTag);
74         formInputTags.append(selectTag);
75     }
76     return formInputTags;
77 }
78
79 TextAutosizer::TextAutosizer(Document* document)
80     : m_document(document)
81 {
82 }
83
84 TextAutosizer::~TextAutosizer()
85 {
86 }
87
88 bool TextAutosizer::processSubtree(RenderObject* layoutRoot)
89 {
90     // FIXME: Text Autosizing should only be enabled when m_document->page()->mainFrame()->view()->useFixedLayout()
91     // is true, but for now it's useful to ignore this so that it can be tested on desktop.
92     if (!m_document->settings() || !m_document->settings()->textAutosizingEnabled() || layoutRoot->view()->printing() || !m_document->page())
93         return false;
94
95     Frame* mainFrame = m_document->page()->mainFrame();
96
97     TextAutosizingWindowInfo windowInfo;
98
99     // Window area, in logical (density-independent) pixels.
100     windowInfo.windowSize = m_document->settings()->textAutosizingWindowSizeOverride();
101     if (windowInfo.windowSize.isEmpty()) {
102         bool includeScrollbars = !InspectorInstrumentation::shouldApplyScreenWidthOverride(mainFrame);
103         windowInfo.windowSize = mainFrame->view()->unscaledVisibleContentSize(includeScrollbars);
104         if (!m_document->settings()->applyDeviceScaleFactorInCompositor())
105             windowInfo.windowSize.scale(1 / m_document->page()->deviceScaleFactor());
106     }
107
108     // Largest area of block that can be visible at once (assuming the main
109     // frame doesn't get scaled to less than overview scale), in CSS pixels.
110     windowInfo.minLayoutSize = mainFrame->view()->layoutSize();
111     for (Frame* frame = m_document->frame(); frame; frame = frame->tree()->parent()) {
112         if (!frame->view()->isInChildFrameWithFrameFlattening())
113             windowInfo.minLayoutSize = windowInfo.minLayoutSize.shrunkTo(frame->view()->layoutSize());
114     }
115
116     // The layoutRoot could be neither a container nor a cluster, so walk up the tree till we find each of these.
117     RenderBlock* container = layoutRoot->isRenderBlock() ? toRenderBlock(layoutRoot) : layoutRoot->containingBlock();
118     while (container && !isAutosizingContainer(container))
119         container = container->containingBlock();
120
121     RenderBlock* cluster = container;
122     while (cluster && (!isAutosizingContainer(cluster) || !isIndependentDescendant(cluster)))
123         cluster = cluster->containingBlock();
124
125     TextAutosizingClusterInfo clusterInfo(cluster);
126     processCluster(clusterInfo, container, layoutRoot, windowInfo);
127     return true;
128 }
129
130 void TextAutosizer::processCluster(TextAutosizingClusterInfo& clusterInfo, RenderBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo)
131 {
132     // Many pages set a max-width on their content. So especially for the
133     // RenderView, instead of just taking the width of |cluster| we find
134     // the lowest common ancestor of the first and last descendant text node of
135     // the cluster (i.e. the deepest wrapper block that contains all the text),
136     // and use its width instead.
137     clusterInfo.blockContainingAllText = findDeepestBlockContainingAllText(clusterInfo.root);
138     float textWidth = clusterInfo.blockContainingAllText->contentLogicalWidth();
139
140     float multiplier = 1;
141     if (clusterShouldBeAutosized(clusterInfo, textWidth)) {
142         int logicalWindowWidth = clusterInfo.root->isHorizontalWritingMode() ? windowInfo.windowSize.width() : windowInfo.windowSize.height();
143         int logicalLayoutWidth = clusterInfo.root->isHorizontalWritingMode() ? windowInfo.minLayoutSize.width() : windowInfo.minLayoutSize.height();
144         // Ignore box width in excess of the layout width, to avoid extreme multipliers.
145         float logicalClusterWidth = std::min<float>(textWidth, logicalLayoutWidth);
146
147         multiplier = logicalClusterWidth / logicalWindowWidth;
148         multiplier *= m_document->settings()->textAutosizingFontScaleFactor();
149         multiplier = std::max(1.0f, multiplier);
150     }
151
152     processContainer(multiplier, container, clusterInfo, subtreeRoot, windowInfo);
153 }
154
155 void TextAutosizer::processContainer(float multiplier, RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo)
156 {
157     ASSERT(isAutosizingContainer(container));
158
159     float localMultiplier = containerShouldBeAutosized(container) ? multiplier: 1;
160
161     RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(subtreeRoot, subtreeRoot);
162     while (descendant) {
163         if (descendant->isText()) {
164             if (localMultiplier != descendant->style()->textAutosizingMultiplier()) {
165                 setMultiplier(descendant, localMultiplier);
166                 setMultiplier(descendant->parent(), localMultiplier); // Parent does line spacing.
167             }
168             // FIXME: Increase list marker size proportionately.
169         } else if (isAutosizingContainer(descendant)) {
170             RenderBlock* descendantBlock = toRenderBlock(descendant);
171             if (isAutosizingCluster(descendantBlock, clusterInfo)) {
172                 TextAutosizingClusterInfo descendantClusterInfo(descendantBlock);
173                 processCluster(descendantClusterInfo, descendantBlock, descendantBlock, windowInfo);
174             } else
175                 processContainer(multiplier, descendantBlock, clusterInfo, descendantBlock, windowInfo);
176         }
177         descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, subtreeRoot);
178     }
179 }
180
181 void TextAutosizer::setMultiplier(RenderObject* renderer, float multiplier)
182 {
183     RefPtr<RenderStyle> newStyle = RenderStyle::clone(renderer->style());
184     newStyle->setTextAutosizingMultiplier(multiplier);
185     renderer->setStyle(newStyle.release());
186 }
187
188 float TextAutosizer::computeAutosizedFontSize(float specifiedSize, float multiplier)
189 {
190     // Somewhat arbitrary "pleasant" font size.
191     const float pleasantSize = 16;
192
193     // Multiply fonts that the page author has specified to be larger than
194     // pleasantSize by less and less, until huge fonts are not increased at all.
195     // For specifiedSize between 0 and pleasantSize we directly apply the
196     // multiplier; hence for specifiedSize == pleasantSize, computedSize will be
197     // multiplier * pleasantSize. For greater specifiedSizes we want to
198     // gradually fade out the multiplier, so for every 1px increase in
199     // specifiedSize beyond pleasantSize we will only increase computedSize
200     // by gradientAfterPleasantSize px until we meet the
201     // computedSize = specifiedSize line, after which we stay on that line (so
202     // then every 1px increase in specifiedSize increases computedSize by 1px).
203     const float gradientAfterPleasantSize = 0.5;
204
205     float computedSize;
206     if (specifiedSize <= pleasantSize)
207         computedSize = multiplier * specifiedSize;
208     else {
209         computedSize = multiplier * pleasantSize + gradientAfterPleasantSize * (specifiedSize - pleasantSize);
210         if (computedSize < specifiedSize)
211             computedSize = specifiedSize;
212     }
213     return computedSize;
214 }
215
216 bool TextAutosizer::isAutosizingContainer(const RenderObject* renderer)
217 {
218     // "Autosizing containers" are the smallest unit for which we can
219     // enable/disable Text Autosizing.
220     // - Must not be inline, as different multipliers on one line looks terrible.
221     //   Exceptions are inline-block and alike elements (inline-table, -webkit-inline-*),
222     //   as they often contain entire multi-line columns of text.
223     // - Must not be list items, as items in the same list should look consistent (*).
224     // - Must not be normal list items, as items in the same list should look
225     //   consistent, unless they are floating or position:absolute/fixed.
226     if (!renderer->isRenderBlock() || (renderer->isInline() && !renderer->style()->isDisplayReplacedType()))
227         return false;
228     if (renderer->isListItem())
229         return renderer->isFloating() || renderer->isOutOfFlowPositioned();
230     // Avoid creating containers for text within text controls, buttons, or <select> buttons.
231     Node* parentNode = renderer->parent() ? renderer->parent()->generatingNode() : 0;
232     if (parentNode && parentNode->isElementNode() && formInputTags().contains(toElement(parentNode)->tagQName()))
233         return false;
234
235     return true;
236 }
237
238 bool TextAutosizer::isNarrowDescendant(const RenderBlock* renderer, TextAutosizingClusterInfo& parentClusterInfo)
239 {
240     ASSERT(isAutosizingContainer(renderer));
241
242     // Autosizing containers that are significantly narrower than the |blockContainingAllText| of
243     // their enclosing cluster may be acting as separate columns, hence must be autosized
244     // separately. For example the 2nd div in:
245     // <body>
246     //     <div style="float: right; width: 50%"></div>
247     //     <div style="width: 50%"></div>
248     // <body>
249     // is the left column, and should be autosized differently from the body.
250     // If however the container is only narrower by 150px or less, it's considered part of
251     // the enclosing cluster. This 150px limit is adjusted whenever a descendant container is
252     // less than 50px narrower than the current limit.
253     const float differenceFromMaxWidthDifference = 50;
254     float contentWidth = renderer->contentLogicalWidth();
255     float clusterTextWidth = parentClusterInfo.blockContainingAllText->contentLogicalWidth();
256     float widthDifference = clusterTextWidth - contentWidth;
257
258     if (widthDifference - parentClusterInfo.maxAllowedDifferenceFromTextWidth > differenceFromMaxWidthDifference)
259         return true;
260
261     parentClusterInfo.maxAllowedDifferenceFromTextWidth = std::max(widthDifference, parentClusterInfo.maxAllowedDifferenceFromTextWidth);
262     return false;
263 }
264
265 bool TextAutosizer::isWiderDescendant(const RenderBlock* renderer, const TextAutosizingClusterInfo& parentClusterInfo)
266 {
267     ASSERT(isAutosizingContainer(renderer));
268
269     // Autosizing containers that are wider than the |blockContainingAllText| of their enclosing
270     // cluster are treated the same way as autosizing clusters to be autosized separately.
271     float contentWidth = renderer->contentLogicalWidth();
272     float clusterTextWidth = parentClusterInfo.blockContainingAllText->contentLogicalWidth();
273     return contentWidth > clusterTextWidth;
274 }
275
276 bool TextAutosizer::isIndependentDescendant(const RenderBlock* renderer)
277 {
278     ASSERT(isAutosizingContainer(renderer));
279
280     // "Autosizing clusters" are special autosizing containers within which we
281     // want to enforce a uniform text size multiplier, in the hopes of making
282     // the major sections of the page look internally consistent.
283     // All their descendants (including other autosizing containers) must share
284     // the same multiplier, except for subtrees which are themselves clusters,
285     // and some of their descendant containers might not be autosized at all
286     // (for example if their height is constrained).
287     // Additionally, clusterShouldBeAutosized requires each cluster to contain a
288     // minimum amount of text, without which it won't be autosized.
289     //
290     // Clusters are chosen using very similar criteria to CSS flow roots, aka
291     // block formatting contexts (http://w3.org/TR/css3-box/#flow-root), since
292     // flow roots correspond to box containers that behave somewhat
293     // independently from their parent (for example they don't overlap floats).
294     // The definition of a flow root also conveniently includes most of the
295     // ways that a box and its children can have significantly different width
296     // from the box's parent (we want to avoid having significantly different
297     // width blocks within a cluster, since the narrower blocks would end up
298     // larger than would otherwise be necessary).
299     return renderer->isRenderView()
300         || renderer->isFloating()
301         || renderer->isOutOfFlowPositioned()
302         || renderer->isTableCell()
303         || renderer->isTableCaption()
304         || renderer->isFlexibleBoxIncludingDeprecated()
305         || renderer->hasColumns()
306         || renderer->containingBlock()->isHorizontalWritingMode() != renderer->isHorizontalWritingMode()
307         || renderer->style()->isDisplayReplacedType();
308     // FIXME: Tables need special handling to multiply all their columns by
309     // the same amount even if they're different widths; so do hasColumns()
310     // containers, and probably flexboxes...
311 }
312
313 bool TextAutosizer::isAutosizingCluster(const RenderBlock* renderer, TextAutosizingClusterInfo& parentClusterInfo)
314 {
315     ASSERT(isAutosizingContainer(renderer));
316
317     return isNarrowDescendant(renderer, parentClusterInfo)
318         || isWiderDescendant(renderer, parentClusterInfo)
319         || isIndependentDescendant(renderer);
320 }
321
322 bool TextAutosizer::containerShouldBeAutosized(const RenderBlock* container)
323 {
324     if (containerContainsOneOfTags(container, formInputTags()))
325         return false;
326
327     if (containerIsRowOfLinks(container))
328         return false;
329
330     // Don't autosize block-level text that can't wrap (as it's likely to
331     // expand sideways and break the page's layout).
332     if (!container->style()->autoWrap())
333         return false;
334
335     return !contentHeightIsConstrained(container);
336 }
337
338 bool TextAutosizer::containerContainsOneOfTags(const RenderBlock* container, const Vector<QualifiedName>& tags)
339 {
340     const RenderObject* renderer = container;
341     while (renderer) {
342         const Node* rendererNode = renderer->node();
343         if (rendererNode && rendererNode->isElementNode()) {
344             if (tags.contains(toElement(rendererNode)->tagQName()))
345                 return true;
346         }
347         renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, container);
348     }
349
350     return false;
351 }
352
353 bool TextAutosizer::containerIsRowOfLinks(const RenderObject* container)
354 {
355     // A "row of links" is a container for which holds:
356     //  1. it should not contain non-link text elements longer than 3 characters
357     //  2. it should contain min. 3 inline links and all links should
358     //     have the same specified font size
359     //  3. it should not contain <br> elements
360     //  4. it should contain only inline elements unless they are containers,
361     //     children of link elements or children of sub-containers.
362     int linkCount = 0;
363     RenderObject* renderer = container->nextInPreOrder(container);
364     float matchingFontSize = -1;
365
366     while (renderer) {
367         if (!isAutosizingContainer(renderer)) {
368             if (renderer->isText() && toRenderText(renderer)->text()->stripWhiteSpace()->length() > 3)
369                 return false;
370             if (!renderer->isInline())
371                 return false;
372             if (renderer->isBR())
373                 return false;
374         }
375         if (renderer->style()->isLink()) {
376             if (matchingFontSize < 0)
377                 matchingFontSize = renderer->style()->specifiedFontSize();
378             else {
379                 if (matchingFontSize != renderer->style()->specifiedFontSize())
380                     return false;
381             }
382
383             linkCount++;
384             // Skip traversing descendants of the link.
385             renderer = renderer->nextInPreOrderAfterChildren(container);
386         } else
387             renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, container);
388     }
389
390     return (linkCount >= 3);
391 }
392
393 bool TextAutosizer::contentHeightIsConstrained(const RenderBlock* container)
394 {
395     // FIXME: Propagate constrainedness down the tree, to avoid inefficiently walking back up from each box.
396     // FIXME: This code needs to take into account vertical writing modes.
397     // FIXME: Consider additional heuristics, such as ignoring fixed heights if the content is already overflowing before autosizing kicks in.
398     for (; container; container = container->containingBlock()) {
399         RenderStyle* style = container->style();
400         if (style->overflowY() >= OSCROLL)
401             return false;
402         if (style->height().isSpecified() || style->maxHeight().isSpecified()) {
403             // Some sites (e.g. wikipedia) set their html and/or body elements to height:100%,
404             // without intending to constrain the height of the content within them.
405             return !container->isRoot() && !container->isBody();
406         }
407         if (container->isFloatingOrOutOfFlowPositioned())
408             return false;
409     }
410     return false;
411 }
412
413 bool TextAutosizer::clusterShouldBeAutosized(TextAutosizingClusterInfo& clusterInfo, float blockWidth)
414 {
415     // Don't autosize clusters that contain less than 4 lines of text (in
416     // practice less lines are required, since measureDescendantTextWidth
417     // assumes that characters are 1em wide, but most characters are narrower
418     // than that, so we're overestimating their contribution to the linecount).
419     //
420     // This is to reduce the likelihood of autosizing things like headers and
421     // footers, which can be quite visually distracting. The rationale is that
422     // if a cluster contains very few lines of text then it's ok to have to zoom
423     // in and pan from side to side to read each line, since if there are very
424     // few lines of text you'll only need to pan across once or twice.
425     const float minLinesOfText = 4;
426     float minTextWidth = blockWidth * minLinesOfText;
427     float textWidth = 0;
428     measureDescendantTextWidth(clusterInfo.blockContainingAllText, clusterInfo, minTextWidth, textWidth);
429     if (textWidth >= minTextWidth)
430         return true;
431     return false;
432 }
433
434 void TextAutosizer::measureDescendantTextWidth(const RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, float minTextWidth, float& textWidth)
435 {
436     bool skipLocalText = !containerShouldBeAutosized(container);
437
438     RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(container, container);
439     while (descendant) {
440         if (!skipLocalText && descendant->isText()) {
441             textWidth += toRenderText(descendant)->renderedTextLength() * descendant->style()->specifiedFontSize();
442         } else if (isAutosizingContainer(descendant)) {
443             RenderBlock* descendantBlock = toRenderBlock(descendant);
444             if (!isAutosizingCluster(descendantBlock, clusterInfo))
445                 measureDescendantTextWidth(descendantBlock, clusterInfo, minTextWidth, textWidth);
446         }
447         if (textWidth >= minTextWidth)
448             return;
449         descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, container);
450     }
451 }
452
453 RenderObject* TextAutosizer::nextInPreOrderSkippingDescendantsOfContainers(const RenderObject* current, const RenderObject* stayWithin)
454 {
455     if (current == stayWithin || !isAutosizingContainer(current))
456         return current->nextInPreOrder(stayWithin);
457     return current->nextInPreOrderAfterChildren(stayWithin);
458 }
459
460 const RenderBlock* TextAutosizer::findDeepestBlockContainingAllText(const RenderBlock* cluster)
461 {
462     size_t firstDepth = 0;
463     const RenderObject* firstTextLeaf = findFirstTextLeafNotInCluster(cluster, firstDepth, FirstToLast);
464     if (!firstTextLeaf)
465         return cluster;
466
467     size_t lastDepth = 0;
468     const RenderObject* lastTextLeaf = findFirstTextLeafNotInCluster(cluster, lastDepth, LastToFirst);
469     ASSERT(lastTextLeaf);
470
471     // Equalize the depths if necessary. Only one of the while loops below will get executed.
472     const RenderObject* firstNode = firstTextLeaf;
473     const RenderObject* lastNode = lastTextLeaf;
474     while (firstDepth > lastDepth) {
475         firstNode = firstNode->parent();
476         --firstDepth;
477     }
478     while (lastDepth > firstDepth) {
479         lastNode = lastNode->parent();
480         --lastDepth;
481     }
482
483     // Go up from both nodes until the parent is the same. Both pointers will point to the LCA then.
484     while (firstNode != lastNode) {
485         firstNode = firstNode->parent();
486         lastNode = lastNode->parent();
487     }
488
489     if (firstNode->isRenderBlock())
490         return toRenderBlock(firstNode);
491
492     // containingBlock() should never leave the cluster, since it only skips ancestors when finding the
493     // container of position:absolute/fixed blocks, and those cannot exist between a cluster and its text
494     // nodes lowest common ancestor as isAutosizingCluster would have made them into their own independent
495     // cluster.
496     RenderBlock* containingBlock = firstNode->containingBlock();
497     ASSERT(containingBlock->isDescendantOf(cluster));
498
499     return containingBlock;
500 }
501
502 const RenderObject* TextAutosizer::findFirstTextLeafNotInCluster(const RenderObject* parent, size_t& depth, TraversalDirection direction)
503 {
504     if (parent->isEmpty())
505         return parent->isText() ? parent : 0;
506
507     ++depth;
508     const RenderObject* child = (direction == FirstToLast) ? parent->firstChild() : parent->lastChild();
509     while (child) {
510         if (!isAutosizingContainer(child) || !isIndependentDescendant(toRenderBlock(child))) {
511             const RenderObject* leaf = findFirstTextLeafNotInCluster(child, depth, direction);
512             if (leaf)
513                 return leaf;
514         }
515         child = (direction == FirstToLast) ? child->nextSibling() : child->previousSibling();
516     }
517     --depth;
518
519     return 0;
520 }
521
522 } // namespace WebCore
523
524 #endif // ENABLE(TEXT_AUTOSIZING)