| Index: Source/core/rendering/TextAutosizer.cpp
|
| diff --git a/Source/core/rendering/TextAutosizer.cpp b/Source/core/rendering/TextAutosizer.cpp
|
| deleted file mode 100644
|
| index 9e68c18324904939274ed23c7d532d0c3e7fcd33..0000000000000000000000000000000000000000
|
| --- a/Source/core/rendering/TextAutosizer.cpp
|
| +++ /dev/null
|
| @@ -1,833 +0,0 @@
|
| -/*
|
| - * Copyright (C) 2012 Google Inc. All rights reserved.
|
| - * Copyright (C) 2012 Apple Inc. All rights reserved.
|
| - *
|
| - * This library is free software; you can redistribute it and/or
|
| - * modify it under the terms of the GNU Library General Public
|
| - * License as published by the Free Software Foundation; either
|
| - * version 2 of the License, or (at your option) any later version.
|
| - *
|
| - * This library is distributed in the hope that it will be useful,
|
| - * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
| - * Library General Public License for more details.
|
| - *
|
| - * You should have received a copy of the GNU Library General Public License
|
| - * along with this library; see the file COPYING.LIB. If not, write to
|
| - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
| - * Boston, MA 02110-1301, USA.
|
| - */
|
| -
|
| -#include "config.h"
|
| -#include "core/rendering/TextAutosizer.h"
|
| -
|
| -#include <algorithm>
|
| -
|
| -#include "core/dom/Document.h"
|
| -#include "core/frame/LocalFrame.h"
|
| -#include "core/frame/Settings.h"
|
| -#include "core/frame/UseCounter.h"
|
| -#include "core/html/HTMLElement.h"
|
| -#include "core/page/Page.h"
|
| -#include "core/rendering/RenderListItem.h"
|
| -#include "core/rendering/RenderObject.h"
|
| -#include "core/rendering/RenderText.h"
|
| -#include "core/rendering/RenderView.h"
|
| -#include "core/rendering/style/RenderStyle.h"
|
| -#include "core/rendering/style/StyleInheritedData.h"
|
| -#include "platform/TraceEvent.h"
|
| -#include "platform/geometry/IntSize.h"
|
| -#include "wtf/StdLibExtras.h"
|
| -
|
| -namespace blink {
|
| -
|
| -#define AUTOSIZING_CLUSTER_HASH
|
| -
|
| -using namespace HTMLNames;
|
| -
|
| -struct TextAutosizingWindowInfo {
|
| - IntSize windowSize;
|
| - IntSize minLayoutSize;
|
| -};
|
| -
|
| -// Represents a POD of a selection of fields for hashing. The fields are selected to detect similar
|
| -// nodes in the Render Tree from the viewpoint of text autosizing.
|
| -struct RenderObjectPodForHash {
|
| - RenderObjectPodForHash()
|
| - : qualifiedNameHash(0)
|
| - , packedStyleProperties(0)
|
| - , width(0)
|
| - {
|
| - }
|
| - ~RenderObjectPodForHash() { }
|
| -
|
| - unsigned qualifiedNameHash;
|
| -
|
| - // Style specific selection of signals
|
| - unsigned packedStyleProperties;
|
| - float width;
|
| -};
|
| -// To allow for efficient hashing using StringHasher.
|
| -COMPILE_ASSERT(!(sizeof(RenderObjectPodForHash) % sizeof(UChar)), RenderObjectPodForHashMultipleOfUchar);
|
| -
|
| -#ifdef AUTOSIZING_DOM_DEBUG_INFO
|
| -static void writeDebugInfo(RenderObject* renderObject, const AtomicString& output)
|
| -{
|
| - Node* node = renderObject->node();
|
| - if (node && node->isElementNode())
|
| - toElement(node)->setAttribute("data-autosizing", output, ASSERT_NO_EXCEPTION);
|
| -}
|
| -#endif
|
| -
|
| -static const Vector<QualifiedName>& formInputTags()
|
| -{
|
| - // Returns the tags for the form input elements.
|
| - DEFINE_STATIC_LOCAL(Vector<QualifiedName>, formInputTags, ());
|
| - if (formInputTags.isEmpty()) {
|
| - formInputTags.append(inputTag);
|
| - formInputTags.append(buttonTag);
|
| - formInputTags.append(selectTag);
|
| - }
|
| - return formInputTags;
|
| -}
|
| -
|
| -static RenderListItem* getAncestorListItem(const RenderObject* renderer)
|
| -{
|
| - RenderObject* ancestor = renderer->parent();
|
| - while (ancestor && (ancestor->isRenderInline() || ancestor->isAnonymousBlock()))
|
| - ancestor = ancestor->parent();
|
| -
|
| - return (ancestor && ancestor->isListItem()) ? toRenderListItem(ancestor) : 0;
|
| -}
|
| -
|
| -static RenderObject* getAncestorList(const RenderObject* renderer)
|
| -{
|
| - // FIXME: Add support for <menu> elements as a possible ancestor of an <li> element,
|
| - // see http://www.whatwg.org/specs/web-apps/current-work/multipage/grouping-content.html#the-li-element
|
| - for (RenderObject* ancestor = renderer->parent(); ancestor; ancestor = ancestor->parent()) {
|
| - Node* parentNode = ancestor->generatingNode();
|
| - if (parentNode && (isHTMLOListElement(*parentNode) || isHTMLUListElement(*parentNode)))
|
| - return ancestor;
|
| - }
|
| - return 0;
|
| -}
|
| -
|
| -static Node* getGeneratingElementNode(const RenderObject* renderer)
|
| -{
|
| - Node* node = renderer->generatingNode();
|
| - return (node && node->isElementNode()) ? node : 0;
|
| -}
|
| -
|
| -static unsigned hashMemory(const void* data, size_t length)
|
| -{
|
| - return StringHasher::computeHash<UChar>(static_cast<const UChar*>(data), length / sizeof(UChar));
|
| -}
|
| -
|
| -static unsigned computeLocalHash(const RenderObject* renderer)
|
| -{
|
| - Node* generatingElementNode = getGeneratingElementNode(renderer);
|
| - ASSERT(generatingElementNode);
|
| -
|
| - RenderObjectPodForHash podForHash;
|
| - podForHash.qualifiedNameHash = QualifiedNameHash::hash(toElement(generatingElementNode)->tagQName());
|
| -
|
| - if (RenderStyle* style = renderer->style()) {
|
| - podForHash.packedStyleProperties = style->direction();
|
| - podForHash.packedStyleProperties |= (style->position() << 1);
|
| - podForHash.packedStyleProperties |= (style->floating() << 4);
|
| - podForHash.packedStyleProperties |= (style->display() << 6);
|
| - podForHash.packedStyleProperties |= (style->width().type() << 11);
|
| - // packedStyleProperties effectively using 15 bits now.
|
| -
|
| - // consider for adding: writing mode, padding.
|
| -
|
| - podForHash.width = style->width().getFloatValue();
|
| - }
|
| -
|
| - return hashMemory(&podForHash, sizeof(podForHash));
|
| -}
|
| -
|
| -TextAutosizer::TextAutosizer(Document* document)
|
| - : m_document(document)
|
| - , m_previouslyAutosized(false)
|
| -{
|
| -}
|
| -
|
| -unsigned TextAutosizer::getCachedHash(const RenderObject* renderer, bool putInCacheIfAbsent)
|
| -{
|
| - HashMap<const RenderObject*, unsigned>::const_iterator it = m_hashCache.find(renderer);
|
| - if (it != m_hashCache.end())
|
| - return it->value;
|
| -
|
| - RenderObject* rendererParent = renderer->parent();
|
| - while (rendererParent && !getGeneratingElementNode(rendererParent))
|
| - rendererParent = rendererParent->parent();
|
| -
|
| - const unsigned parentHashValue = rendererParent ? getCachedHash(rendererParent, true) : 0;
|
| - const unsigned hashes[2] = { parentHashValue, computeLocalHash(renderer) };
|
| - const unsigned combinedHashValue = hashMemory(hashes, sizeof(hashes));
|
| - if (putInCacheIfAbsent)
|
| - m_hashCache.add(renderer, combinedHashValue);
|
| - return combinedHashValue;
|
| -}
|
| -
|
| -bool TextAutosizer::isApplicable() const
|
| -{
|
| - return m_document->settings()
|
| - && m_document->settings()->textAutosizingEnabled()
|
| - && m_document->page()
|
| - && m_document->page()->mainFrame()
|
| - && m_document->page()->deprecatedLocalMainFrame()->loader().stateMachine()->committedFirstRealDocumentLoad();
|
| -}
|
| -
|
| -void TextAutosizer::recalculateMultipliers()
|
| -{
|
| - if (!isApplicable() && !m_previouslyAutosized)
|
| - return;
|
| -
|
| - RenderObject* renderer = m_document->renderView();
|
| - while (renderer) {
|
| - if (renderer->style() && renderer->style()->textAutosizingMultiplier() != 1)
|
| - setMultiplier(renderer, 1);
|
| - renderer = renderer->nextInPreOrder();
|
| - }
|
| - m_previouslyAutosized = false;
|
| -}
|
| -
|
| -bool TextAutosizer::processSubtree(RenderObject* layoutRoot)
|
| -{
|
| - TRACE_EVENT0("blink", "TextAutosizer: check if needed");
|
| -
|
| - if (!isApplicable() || layoutRoot->view()->document().printing())
|
| - return false;
|
| -
|
| - LocalFrame* mainFrame = m_document->page()->deprecatedLocalMainFrame();
|
| - TextAutosizingWindowInfo windowInfo;
|
| -
|
| - // Window area, in logical (density-independent) pixels.
|
| - windowInfo.windowSize = m_document->settings()->textAutosizingWindowSizeOverride();
|
| - if (windowInfo.windowSize.isEmpty())
|
| - windowInfo.windowSize = mainFrame->view()->unscaledVisibleContentSize(IncludeScrollbars);
|
| -
|
| - // Largest area of block that can be visible at once (assuming the main
|
| - // frame doesn't get scaled to less than overview scale), in CSS pixels.
|
| - windowInfo.minLayoutSize = mainFrame->view()->layoutSize();
|
| - for (Frame* frame = m_document->frame(); frame; frame = frame->tree().parent()) {
|
| - windowInfo.minLayoutSize = windowInfo.minLayoutSize.shrunkTo(toLocalFrame(frame)->view()->layoutSize());
|
| - }
|
| -
|
| - // The layoutRoot could be neither a container nor a cluster, so walk up the tree till we find each of these.
|
| - RenderBlock* container = layoutRoot->isRenderBlock() ? toRenderBlock(layoutRoot) : layoutRoot->containingBlock();
|
| - while (container && !isAutosizingContainer(container))
|
| - container = container->containingBlock();
|
| -
|
| - RenderBlock* cluster = container;
|
| - while (cluster && (!isAutosizingContainer(cluster) || !isIndependentDescendant(cluster)))
|
| - cluster = cluster->containingBlock();
|
| -
|
| - // Skip autosizing for orphaned trees, or if it will have no effect.
|
| - // Note: this might suppress autosizing of an inner cluster with a different writing mode.
|
| - // It's not clear what the correct behavior is for mixed writing modes anyway.
|
| - if (!cluster || clusterMultiplier(cluster->style()->writingMode(), windowInfo,
|
| - std::numeric_limits<float>::infinity()) == 1.0f)
|
| - return false;
|
| -
|
| - TRACE_EVENT0("blink", "TextAutosizer: process root cluster");
|
| - UseCounter::count(*m_document, UseCounter::TextAutosizing);
|
| -
|
| - TextAutosizingClusterInfo clusterInfo(cluster);
|
| - processCluster(clusterInfo, container, layoutRoot, windowInfo);
|
| -
|
| -#ifdef AUTOSIZING_CLUSTER_HASH
|
| - // Second pass to autosize stale non-autosized clusters for consistency.
|
| - secondPassProcessStaleNonAutosizedClusters();
|
| - m_hashCache.clear();
|
| - m_hashToMultiplier.clear();
|
| - m_hashesToAutosizeSecondPass.clear();
|
| - m_nonAutosizedClusters.clear();
|
| -#endif
|
| - m_previouslyAutosized = true;
|
| - return true;
|
| -}
|
| -
|
| -float TextAutosizer::clusterMultiplier(WritingMode writingMode, const TextAutosizingWindowInfo& windowInfo, float textWidth) const
|
| -{
|
| - int logicalWindowWidth = isHorizontalWritingMode(writingMode) ? windowInfo.windowSize.width() : windowInfo.windowSize.height();
|
| - int logicalLayoutWidth = isHorizontalWritingMode(writingMode) ? windowInfo.minLayoutSize.width() : windowInfo.minLayoutSize.height();
|
| - // Ignore box width in excess of the layout width, to avoid extreme multipliers.
|
| - float logicalClusterWidth = std::min<float>(textWidth, logicalLayoutWidth);
|
| -
|
| - float multiplier = logicalClusterWidth / logicalWindowWidth;
|
| - multiplier *= m_document->settings()->accessibilityFontScaleFactor();
|
| -
|
| - // If the page has a meta viewport or @viewport, don't apply the device scale adjustment.
|
| - const ViewportDescription& viewportDescription = m_document->page()->deprecatedLocalMainFrame()->document()->viewportDescription();
|
| - if (!viewportDescription.isSpecifiedByAuthor()) {
|
| - float deviceScaleAdjustment = m_document->settings()->deviceScaleAdjustment();
|
| - multiplier *= deviceScaleAdjustment;
|
| - }
|
| - return std::max(1.0f, multiplier);
|
| -}
|
| -
|
| -void TextAutosizer::processClusterInternal(TextAutosizingClusterInfo& clusterInfo, RenderBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo, float multiplier)
|
| -{
|
| - processContainer(multiplier, container, clusterInfo, subtreeRoot, windowInfo);
|
| -#ifdef AUTOSIZING_DOM_DEBUG_INFO
|
| - writeDebugInfo(clusterInfo.root, AtomicString(String::format("cluster:%f", multiplier)));
|
| -#endif
|
| -
|
| - Vector<Vector<TextAutosizingClusterInfo> > narrowDescendantsGroups;
|
| - getNarrowDescendantsGroupedByWidth(clusterInfo, narrowDescendantsGroups);
|
| - for (size_t i = 0; i < narrowDescendantsGroups.size(); ++i)
|
| - processCompositeCluster(narrowDescendantsGroups[i], windowInfo);
|
| -}
|
| -
|
| -unsigned TextAutosizer::computeCompositeClusterHash(Vector<TextAutosizingClusterInfo>& clusterInfos)
|
| -{
|
| - if (clusterInfos.size() == 1 && getGeneratingElementNode(clusterInfos[0].root))
|
| - return getCachedHash(clusterInfos[0].root, false);
|
| -
|
| - // FIXME: consider hashing clusters for which clusterInfos.size() > 1
|
| - return 0;
|
| -}
|
| -
|
| -void TextAutosizer::addNonAutosizedCluster(unsigned key, TextAutosizingClusterInfo& value)
|
| -{
|
| - HashMap<unsigned, OwnPtr<Vector<TextAutosizingClusterInfo> > >::const_iterator it = m_nonAutosizedClusters.find(key);
|
| - if (it == m_nonAutosizedClusters.end()) {
|
| - m_nonAutosizedClusters.add(key, adoptPtr(new Vector<TextAutosizingClusterInfo>(1, value)));
|
| - return;
|
| - }
|
| - it->value->append(value);
|
| -}
|
| -
|
| -float TextAutosizer::computeMultiplier(Vector<TextAutosizingClusterInfo>& clusterInfos, const TextAutosizingWindowInfo& windowInfo, float textWidth)
|
| -{
|
| -#ifdef AUTOSIZING_CLUSTER_HASH
|
| - // When hashing is enabled this function returns a multiplier based on previously seen clusters.
|
| - // It will return a non-unit multiplier if a cluster with the same hash value has been previously
|
| - // autosized.
|
| - unsigned clusterHash = computeCompositeClusterHash(clusterInfos);
|
| -#else
|
| - unsigned clusterHash = 0;
|
| -#endif
|
| -
|
| - if (clusterHash) {
|
| - HashMap<unsigned, float>::iterator it = m_hashToMultiplier.find(clusterHash);
|
| - if (it != m_hashToMultiplier.end())
|
| - return it->value;
|
| - }
|
| -
|
| - if (compositeClusterShouldBeAutosized(clusterInfos, textWidth)) {
|
| - float multiplier = clusterMultiplier(clusterInfos[0].root->style()->writingMode(), windowInfo, textWidth);
|
| - if (clusterHash) {
|
| - if (multiplier > 1 && m_nonAutosizedClusters.contains(clusterHash))
|
| - m_hashesToAutosizeSecondPass.append(clusterHash);
|
| - m_hashToMultiplier.add(clusterHash, multiplier);
|
| - }
|
| - return multiplier;
|
| - }
|
| -
|
| - if (clusterHash)
|
| - addNonAutosizedCluster(clusterHash, clusterInfos[0]);
|
| -
|
| - return 1.0f;
|
| -}
|
| -
|
| -void TextAutosizer::processCluster(TextAutosizingClusterInfo& clusterInfo, RenderBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo)
|
| -{
|
| - // Many pages set a max-width on their content. So especially for the RenderView, instead of
|
| - // just taking the width of |cluster| we find the lowest common ancestor of the first and last
|
| - // descendant text node of the cluster (i.e. the deepest wrapper block that contains all the
|
| - // text), and use its width instead.
|
| - clusterInfo.blockContainingAllText = findDeepestBlockContainingAllText(clusterInfo.root);
|
| - float textWidth = clusterInfo.blockContainingAllText->contentLogicalWidth().toFloat();
|
| -
|
| - Vector<TextAutosizingClusterInfo> clusterInfos(1, clusterInfo);
|
| - float multiplier = computeMultiplier(clusterInfos, windowInfo, textWidth);
|
| -
|
| - processClusterInternal(clusterInfo, container, subtreeRoot, windowInfo, multiplier);
|
| -}
|
| -
|
| -void TextAutosizer::processCompositeCluster(Vector<TextAutosizingClusterInfo>& clusterInfos, const TextAutosizingWindowInfo& windowInfo)
|
| -{
|
| - if (clusterInfos.isEmpty())
|
| - return;
|
| -
|
| - float maxTextWidth = 0;
|
| - for (size_t i = 0; i < clusterInfos.size(); ++i) {
|
| - TextAutosizingClusterInfo& clusterInfo = clusterInfos[i];
|
| - clusterInfo.blockContainingAllText = findDeepestBlockContainingAllText(clusterInfo.root);
|
| - maxTextWidth = max<float>(maxTextWidth, clusterInfo.blockContainingAllText->contentLogicalWidth().toFloat());
|
| - }
|
| -
|
| - float multiplier = computeMultiplier(clusterInfos, windowInfo, maxTextWidth);
|
| - for (size_t i = 0; i < clusterInfos.size(); ++i) {
|
| - ASSERT(clusterInfos[i].root->style()->writingMode() == clusterInfos[0].root->style()->writingMode());
|
| - processClusterInternal(clusterInfos[i], clusterInfos[i].root, clusterInfos[i].root, windowInfo, multiplier);
|
| - }
|
| -}
|
| -
|
| -void TextAutosizer::secondPassProcessStaleNonAutosizedClusters()
|
| -{
|
| - for (size_t i = 0; i < m_hashesToAutosizeSecondPass.size(); ++i) {
|
| - unsigned hash = m_hashesToAutosizeSecondPass[i];
|
| - float multiplier = m_hashToMultiplier.get(hash);
|
| - Vector<TextAutosizingClusterInfo>* val = m_nonAutosizedClusters.get(hash);
|
| - for (Vector<TextAutosizingClusterInfo>::iterator it2 = val->begin(); it2 != val->end(); ++it2)
|
| - processStaleContainer(multiplier, (*it2).root, *it2);
|
| - }
|
| -}
|
| -
|
| -void TextAutosizer::processStaleContainer(float multiplier, RenderBlock* cluster, TextAutosizingClusterInfo& clusterInfo)
|
| -{
|
| - ASSERT(isAutosizingContainer(cluster));
|
| -
|
| - // This method is different from processContainer() mainly in that it does not recurse into sub-clusters.
|
| - // Multiplier updates are restricted to the specified cluster only. Also the multiplier > 1 by construction
|
| - // of m_hashesToAutosizeSecondPass, so we don't need to check it explicitly.
|
| - float localMultiplier = containerShouldBeAutosized(cluster) ? multiplier : 1;
|
| -
|
| - RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(cluster, cluster);
|
| - while (descendant) {
|
| - if (descendant->isText()) {
|
| - if (localMultiplier != 1 && descendant->style()->textAutosizingMultiplier() == 1) {
|
| - setMultiplier(descendant, localMultiplier);
|
| - setMultiplier(descendant->parent(), localMultiplier); // Parent does line spacing.
|
| - }
|
| - } else if (isAutosizingContainer(descendant)) {
|
| - RenderBlock* descendantBlock = toRenderBlock(descendant);
|
| - if (!isAutosizingCluster(descendantBlock, clusterInfo))
|
| - processStaleContainer(multiplier, descendantBlock, clusterInfo);
|
| - }
|
| - descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, cluster);
|
| - }
|
| -}
|
| -
|
| -void TextAutosizer::processContainer(float multiplier, RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo)
|
| -{
|
| - ASSERT(isAutosizingContainer(container));
|
| -#ifdef AUTOSIZING_DOM_DEBUG_INFO
|
| - writeDebugInfo(container, "container");
|
| -#endif
|
| -
|
| - float localMultiplier = (multiplier > 1 && containerShouldBeAutosized(container)) ? multiplier: 1;
|
| -
|
| - RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(subtreeRoot, subtreeRoot);
|
| - while (descendant) {
|
| - if (descendant->isText()) {
|
| - if (localMultiplier != 1 && descendant->style()->textAutosizingMultiplier() == 1) {
|
| - setMultiplier(descendant, localMultiplier);
|
| - setMultiplier(descendant->parent(), localMultiplier); // Parent does line spacing.
|
| -
|
| - if (RenderListItem* listItemAncestor = getAncestorListItem(descendant)) {
|
| - if (RenderObject* list = getAncestorList(listItemAncestor)) {
|
| - if (list->style()->textAutosizingMultiplier() == 1)
|
| - setMultiplierForList(list, localMultiplier);
|
| - }
|
| - }
|
| - }
|
| - } else if (isAutosizingContainer(descendant)) {
|
| - RenderBlock* descendantBlock = toRenderBlock(descendant);
|
| - TextAutosizingClusterInfo descendantClusterInfo(descendantBlock);
|
| - if (isWiderDescendant(descendantBlock, clusterInfo) || isIndependentDescendant(descendantBlock))
|
| - processCluster(descendantClusterInfo, descendantBlock, descendantBlock, windowInfo);
|
| - else if (isNarrowDescendant(descendantBlock, clusterInfo)) {
|
| - // Narrow descendants are processed together later to be able to apply the same multiplier
|
| - // to each of them if necessary.
|
| - clusterInfo.narrowDescendants.append(descendantClusterInfo);
|
| - } else
|
| - processContainer(multiplier, descendantBlock, clusterInfo, descendantBlock, windowInfo);
|
| - }
|
| - descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, subtreeRoot);
|
| - }
|
| -}
|
| -
|
| -void TextAutosizer::setMultiplier(RenderObject* renderer, float multiplier)
|
| -{
|
| - RefPtr<RenderStyle> newStyle = RenderStyle::clone(renderer->style());
|
| - newStyle->setTextAutosizingMultiplier(multiplier);
|
| - newStyle->setUnique();
|
| - renderer->setStyle(newStyle.release());
|
| -}
|
| -
|
| -void TextAutosizer::setMultiplierForList(RenderObject* renderer, float multiplier)
|
| -{
|
| -#if ENABLE(ASSERT)
|
| - Node* parentNode = renderer->generatingNode();
|
| - ASSERT(parentNode);
|
| - ASSERT(isHTMLOListElement(parentNode) || isHTMLUListElement(parentNode));
|
| -#endif
|
| - setMultiplier(renderer, multiplier);
|
| -
|
| - // Make sure all list items are autosized consistently.
|
| - for (RenderObject* child = renderer->slowFirstChild(); child; child = child->nextSibling()) {
|
| - if (child->isListItem() && child->style()->textAutosizingMultiplier() == 1)
|
| - setMultiplier(child, multiplier);
|
| - }
|
| -}
|
| -
|
| -bool TextAutosizer::isAutosizingContainer(const RenderObject* renderer)
|
| -{
|
| - // "Autosizing containers" are the smallest unit for which we can
|
| - // enable/disable Text Autosizing.
|
| - // - Must not be inline, as different multipliers on one line looks terrible.
|
| - // Exceptions are inline-block and alike elements (inline-table, -webkit-inline-*),
|
| - // as they often contain entire multi-line columns of text.
|
| - // - Must not be list items, as items in the same list should look consistent (*).
|
| - // - Must not be normal list items, as items in the same list should look
|
| - // consistent, unless they are floating or position:absolute/fixed.
|
| - Node* node = renderer->generatingNode();
|
| - if ((node && !node->hasChildren())
|
| - || !renderer->isRenderBlock()
|
| - || (renderer->isInline() && !renderer->style()->isDisplayReplacedType()))
|
| - return false;
|
| - if (renderer->isListItem())
|
| - return renderer->isFloating() || renderer->isOutOfFlowPositioned();
|
| - // Avoid creating containers for text within text controls, buttons, or <select> buttons.
|
| - Node* parentNode = renderer->parent() ? renderer->parent()->generatingNode() : 0;
|
| - if (parentNode && parentNode->isElementNode() && formInputTags().contains(toElement(parentNode)->tagQName()))
|
| - return false;
|
| -
|
| - return true;
|
| -}
|
| -
|
| -bool TextAutosizer::isNarrowDescendant(const RenderBlock* renderer, TextAutosizingClusterInfo& parentClusterInfo)
|
| -{
|
| - ASSERT(isAutosizingContainer(renderer));
|
| -
|
| - // Autosizing containers that are significantly narrower than the |blockContainingAllText| of
|
| - // their enclosing cluster may be acting as separate columns, hence must be autosized
|
| - // separately. For example the 2nd div in:
|
| - // <body>
|
| - // <div style="float: right; width: 50%"></div>
|
| - // <div style="width: 50%"></div>
|
| - // <body>
|
| - // is the left column, and should be autosized differently from the body.
|
| - // If however the container is only narrower by 150px or less, it's considered part of
|
| - // the enclosing cluster. This 150px limit is adjusted whenever a descendant container is
|
| - // less than 50px narrower than the current limit.
|
| - const float differenceFromMaxWidthDifference = 50;
|
| - LayoutUnit contentWidth = renderer->contentLogicalWidth();
|
| - LayoutUnit clusterTextWidth = parentClusterInfo.blockContainingAllText->contentLogicalWidth();
|
| - LayoutUnit widthDifference = clusterTextWidth - contentWidth;
|
| -
|
| - if (widthDifference - parentClusterInfo.maxAllowedDifferenceFromTextWidth > differenceFromMaxWidthDifference)
|
| - return true;
|
| -
|
| - parentClusterInfo.maxAllowedDifferenceFromTextWidth = std::max(widthDifference.toFloat(), parentClusterInfo.maxAllowedDifferenceFromTextWidth);
|
| - return false;
|
| -}
|
| -
|
| -bool TextAutosizer::isWiderDescendant(const RenderBlock* renderer, const TextAutosizingClusterInfo& parentClusterInfo)
|
| -{
|
| - ASSERT(isAutosizingContainer(renderer));
|
| -
|
| - // Autosizing containers that are wider than the |blockContainingAllText| of their enclosing
|
| - // cluster are treated the same way as autosizing clusters to be autosized separately.
|
| - LayoutUnit contentWidth = renderer->contentLogicalWidth();
|
| - LayoutUnit clusterTextWidth = parentClusterInfo.blockContainingAllText->contentLogicalWidth();
|
| - return contentWidth > clusterTextWidth;
|
| -}
|
| -
|
| -bool TextAutosizer::isIndependentDescendant(const RenderBlock* renderer)
|
| -{
|
| - ASSERT(isAutosizingContainer(renderer));
|
| -
|
| - // "Autosizing clusters" are special autosizing containers within which we
|
| - // want to enforce a uniform text size multiplier, in the hopes of making
|
| - // the major sections of the page look internally consistent.
|
| - // All their descendants (including other autosizing containers) must share
|
| - // the same multiplier, except for subtrees which are themselves clusters,
|
| - // and some of their descendant containers might not be autosized at all
|
| - // (for example if their height is constrained).
|
| - // Additionally, clusterShouldBeAutosized requires each cluster to contain a
|
| - // minimum amount of text, without which it won't be autosized.
|
| - //
|
| - // Clusters are chosen using very similar criteria to CSS flow roots, aka
|
| - // block formatting contexts (http://w3.org/TR/css3-box/#flow-root), since
|
| - // flow roots correspond to box containers that behave somewhat
|
| - // independently from their parent (for example they don't overlap floats).
|
| - // The definition of a flow root also conveniently includes most of the
|
| - // ways that a box and its children can have significantly different width
|
| - // from the box's parent (we want to avoid having significantly different
|
| - // width blocks within a cluster, since the narrower blocks would end up
|
| - // larger than would otherwise be necessary).
|
| - RenderBlock* containingBlock = renderer->containingBlock();
|
| - return renderer->isRenderView()
|
| - || renderer->isFloating()
|
| - || renderer->isOutOfFlowPositioned()
|
| - || renderer->isTableCell()
|
| - || renderer->isTableCaption()
|
| - || renderer->isFlexibleBoxIncludingDeprecated()
|
| - || renderer->hasColumns()
|
| - || (containingBlock && containingBlock->isHorizontalWritingMode() != renderer->isHorizontalWritingMode())
|
| - || renderer->style()->isDisplayReplacedType()
|
| - || renderer->isTextArea()
|
| - || renderer->style()->userModify() != READ_ONLY;
|
| - // FIXME: Tables need special handling to multiply all their columns by
|
| - // the same amount even if they're different widths; so do hasColumns()
|
| - // containers, and probably flexboxes...
|
| -}
|
| -
|
| -bool TextAutosizer::isAutosizingCluster(const RenderBlock* renderer, TextAutosizingClusterInfo& parentClusterInfo)
|
| -{
|
| - ASSERT(isAutosizingContainer(renderer));
|
| -
|
| - return isNarrowDescendant(renderer, parentClusterInfo)
|
| - || isWiderDescendant(renderer, parentClusterInfo)
|
| - || isIndependentDescendant(renderer);
|
| -}
|
| -
|
| -bool TextAutosizer::containerShouldBeAutosized(const RenderBlock* container)
|
| -{
|
| - if (containerContainsOneOfTags(container, formInputTags()))
|
| - return false;
|
| -
|
| - if (containerIsRowOfLinks(container))
|
| - return false;
|
| -
|
| - // Don't autosize block-level text that can't wrap (as it's likely to
|
| - // expand sideways and break the page's layout).
|
| - if (!container->style()->autoWrap())
|
| - return false;
|
| -
|
| - return !contentHeightIsConstrained(container);
|
| -}
|
| -
|
| -bool TextAutosizer::containerContainsOneOfTags(const RenderBlock* container, const Vector<QualifiedName>& tags)
|
| -{
|
| - const RenderObject* renderer = container;
|
| - while (renderer) {
|
| - const Node* rendererNode = renderer->node();
|
| - if (rendererNode && rendererNode->isElementNode()) {
|
| - if (tags.contains(toElement(rendererNode)->tagQName()))
|
| - return true;
|
| - }
|
| - renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, container);
|
| - }
|
| -
|
| - return false;
|
| -}
|
| -
|
| -bool TextAutosizer::containerIsRowOfLinks(const RenderObject* container)
|
| -{
|
| - // A "row of links" is a container for which holds:
|
| - // 1. it should not contain non-link text elements longer than 3 characters
|
| - // 2. it should contain min. 3 inline links and all links should
|
| - // have the same specified font size
|
| - // 3. it should not contain <br> elements
|
| - // 4. it should contain only inline elements unless they are containers,
|
| - // children of link elements or children of sub-containers.
|
| - int linkCount = 0;
|
| - RenderObject* renderer = container->nextInPreOrder(container);
|
| - float matchingFontSize = -1;
|
| -
|
| - while (renderer) {
|
| - if (!isAutosizingContainer(renderer)) {
|
| - if (renderer->isText() && toRenderText(renderer)->text().impl()->stripWhiteSpace()->length() > 3)
|
| - return false;
|
| - if (!renderer->isInline())
|
| - return false;
|
| - if (renderer->isBR())
|
| - return false;
|
| - }
|
| - if (renderer->style()->isLink()) {
|
| - if (matchingFontSize < 0)
|
| - matchingFontSize = renderer->style()->specifiedFontSize();
|
| - else {
|
| - if (matchingFontSize != renderer->style()->specifiedFontSize())
|
| - return false;
|
| - }
|
| -
|
| - linkCount++;
|
| - // Skip traversing descendants of the link.
|
| - renderer = renderer->nextInPreOrderAfterChildren(container);
|
| - } else
|
| - renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, container);
|
| - }
|
| -
|
| - return (linkCount >= 3);
|
| -}
|
| -
|
| -bool TextAutosizer::contentHeightIsConstrained(const RenderBlock* container)
|
| -{
|
| - // FIXME: Propagate constrainedness down the tree, to avoid inefficiently walking back up from each box.
|
| - // FIXME: This code needs to take into account vertical writing modes.
|
| - // FIXME: Consider additional heuristics, such as ignoring fixed heights if the content is already overflowing before autosizing kicks in.
|
| - for (; container; container = container->containingBlock()) {
|
| - RenderStyle* style = container->style();
|
| - if (style->overflowY() >= OSCROLL)
|
| - return false;
|
| - if (style->height().isSpecified() || style->maxHeight().isSpecified() || container->isOutOfFlowPositioned()) {
|
| - // Some sites (e.g. wikipedia) set their html and/or body elements to height:100%,
|
| - // without intending to constrain the height of the content within them.
|
| - return !container->isDocumentElement() && !container->isBody();
|
| - }
|
| - if (container->isFloating())
|
| - return false;
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -bool TextAutosizer::compositeClusterShouldBeAutosized(Vector<TextAutosizingClusterInfo>& clusterInfos, float blockWidth)
|
| -{
|
| - // Don't autosize clusters that contain less than 4 lines of text (in
|
| - // practice less lines are required, since measureDescendantTextWidth
|
| - // assumes that characters are 1em wide, but most characters are narrower
|
| - // than that, so we're overestimating their contribution to the linecount).
|
| - //
|
| - // This is to reduce the likelihood of autosizing things like headers and
|
| - // footers, which can be quite visually distracting. The rationale is that
|
| - // if a cluster contains very few lines of text then it's ok to have to zoom
|
| - // in and pan from side to side to read each line, since if there are very
|
| - // few lines of text you'll only need to pan across once or twice.
|
| - //
|
| - // An exception to the 4 lines of text are the textarea and contenteditable
|
| - // clusters, which are always autosized by default (i.e. threated as if they
|
| - // contain more than 4 lines of text). This is to ensure that the text does
|
| - // not suddenly get autosized when the user enters more than 4 lines of text.
|
| - float totalTextWidth = 0;
|
| - const float minLinesOfText = 4;
|
| - float minTextWidth = blockWidth * minLinesOfText;
|
| - for (size_t i = 0; i < clusterInfos.size(); ++i) {
|
| - if (clusterInfos[i].root->isTextArea() || (clusterInfos[i].root->style() && clusterInfos[i].root->style()->userModify() != READ_ONLY))
|
| - return true;
|
| - measureDescendantTextWidth(clusterInfos[i].blockContainingAllText, clusterInfos[i], minTextWidth, totalTextWidth);
|
| - if (totalTextWidth >= minTextWidth)
|
| - return true;
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -void TextAutosizer::measureDescendantTextWidth(const RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, float minTextWidth, float& textWidth)
|
| -{
|
| - bool skipLocalText = !containerShouldBeAutosized(container);
|
| -
|
| - RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(container, container);
|
| - while (descendant) {
|
| - if (!skipLocalText && descendant->isText()) {
|
| - textWidth += toRenderText(descendant)->renderedTextLength() * descendant->style()->specifiedFontSize();
|
| - } else if (isAutosizingContainer(descendant)) {
|
| - RenderBlock* descendantBlock = toRenderBlock(descendant);
|
| - if (!isAutosizingCluster(descendantBlock, clusterInfo))
|
| - measureDescendantTextWidth(descendantBlock, clusterInfo, minTextWidth, textWidth);
|
| - }
|
| - if (textWidth >= minTextWidth)
|
| - return;
|
| - descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, container);
|
| - }
|
| -}
|
| -
|
| -RenderObject* TextAutosizer::nextInPreOrderSkippingDescendantsOfContainers(const RenderObject* current, const RenderObject* stayWithin)
|
| -{
|
| - if (current == stayWithin || !isAutosizingContainer(current))
|
| - return current->nextInPreOrder(stayWithin);
|
| - return current->nextInPreOrderAfterChildren(stayWithin);
|
| -}
|
| -
|
| -const RenderBlock* TextAutosizer::findDeepestBlockContainingAllText(const RenderBlock* cluster)
|
| -{
|
| - size_t firstDepth = 0;
|
| - const RenderObject* firstTextLeaf = findFirstTextLeafNotInCluster(cluster, firstDepth, FirstToLast);
|
| - if (!firstTextLeaf)
|
| - return cluster;
|
| -
|
| - size_t lastDepth = 0;
|
| - const RenderObject* lastTextLeaf = findFirstTextLeafNotInCluster(cluster, lastDepth, LastToFirst);
|
| - ASSERT(lastTextLeaf);
|
| -
|
| - // Equalize the depths if necessary. Only one of the while loops below will get executed.
|
| - const RenderObject* firstNode = firstTextLeaf;
|
| - const RenderObject* lastNode = lastTextLeaf;
|
| - while (firstDepth > lastDepth) {
|
| - firstNode = firstNode->parent();
|
| - --firstDepth;
|
| - }
|
| - while (lastDepth > firstDepth) {
|
| - lastNode = lastNode->parent();
|
| - --lastDepth;
|
| - }
|
| -
|
| - // Go up from both nodes until the parent is the same. Both pointers will point to the LCA then.
|
| - while (firstNode != lastNode) {
|
| - firstNode = firstNode->parent();
|
| - lastNode = lastNode->parent();
|
| - }
|
| -
|
| - if (firstNode->isRenderBlock())
|
| - return toRenderBlock(firstNode);
|
| -
|
| - // containingBlock() should never leave the cluster, since it only skips ancestors when finding the
|
| - // container of position:absolute/fixed blocks, and those cannot exist between a cluster and its text
|
| - // nodes lowest common ancestor as isAutosizingCluster would have made them into their own independent
|
| - // cluster.
|
| - RenderBlock* containingBlock = firstNode->containingBlock();
|
| - ASSERT(containingBlock->isDescendantOf(cluster));
|
| -
|
| - return containingBlock;
|
| -}
|
| -
|
| -const RenderObject* TextAutosizer::findFirstTextLeafNotInCluster(const RenderObject* parent, size_t& depth, TraversalDirection direction)
|
| -{
|
| - if (parent->isText())
|
| - return parent;
|
| -
|
| - ++depth;
|
| - const RenderObject* child = (direction == FirstToLast) ? parent->slowFirstChild() : parent->slowLastChild();
|
| - while (child) {
|
| - if (!isAutosizingContainer(child) || !isIndependentDescendant(toRenderBlock(child))) {
|
| - const RenderObject* leaf = findFirstTextLeafNotInCluster(child, depth, direction);
|
| - if (leaf)
|
| - return leaf;
|
| - }
|
| - child = (direction == FirstToLast) ? child->nextSibling() : child->previousSibling();
|
| - }
|
| - --depth;
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -namespace {
|
| -
|
| -// Compares the width of the specified cluster's roots in descending order.
|
| -bool clusterWiderThanComparisonFn(const TextAutosizingClusterInfo& first, const TextAutosizingClusterInfo& second)
|
| -{
|
| - return first.root->contentLogicalWidth() > second.root->contentLogicalWidth();
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -void TextAutosizer::getNarrowDescendantsGroupedByWidth(const TextAutosizingClusterInfo& parentClusterInfo, Vector<Vector<TextAutosizingClusterInfo> >& groups)
|
| -{
|
| - ASSERT(parentClusterInfo.blockContainingAllText);
|
| - ASSERT(groups.isEmpty());
|
| -
|
| - Vector<TextAutosizingClusterInfo> clusterInfos(parentClusterInfo.narrowDescendants);
|
| - if (clusterInfos.isEmpty())
|
| - return;
|
| -
|
| - std::sort(clusterInfos.begin(), clusterInfos.end(), &clusterWiderThanComparisonFn);
|
| - groups.grow(1);
|
| -
|
| - // If the width difference between two consecutive elements of |clusterInfos| is greater than
|
| - // this empirically determined value, the next element should start a new group.
|
| - const float maxWidthDifferenceWithinGroup = 100;
|
| - for (size_t i = 0; i < clusterInfos.size(); ++i) {
|
| - groups.last().append(clusterInfos[i]);
|
| -
|
| - if (i + 1 < clusterInfos.size()) {
|
| - LayoutUnit currentWidth = clusterInfos[i].root->contentLogicalWidth();
|
| - LayoutUnit nextWidth = clusterInfos[i + 1].root->contentLogicalWidth();
|
| - if (currentWidth - nextWidth > maxWidthDifferenceWithinGroup)
|
| - groups.grow(groups.size() + 1);
|
| - }
|
| - }
|
| -}
|
| -
|
| -void TextAutosizer::trace(Visitor* visitor)
|
| -{
|
| - visitor->trace(m_document);
|
| -}
|
| -
|
| -} // namespace blink
|
|
|