/* * 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" #if ENABLE(TEXT_AUTOSIZING) #include "TextAutosizer.h" #include "Document.h" #include "HTMLElement.h" #include "IntSize.h" #include "RenderObject.h" #include "RenderStyle.h" #include "RenderText.h" #include "RenderView.h" #include "Settings.h" #include "StyleInheritedData.h" #include #include #include namespace WebCore { using namespace HTMLNames; struct TextAutosizingWindowInfo { IntSize windowSize; IntSize minLayoutSize; }; // Represents cluster related data. Instances should not persist between calls to processSubtree. struct TextAutosizingClusterInfo { explicit TextAutosizingClusterInfo(RenderBlock* root) : root(root) , blockContainingAllText(0) , maxAllowedDifferenceFromTextWidth(150) { } RenderBlock* root; const RenderBlock* blockContainingAllText; // Upper limit on the difference between the width of the cluster's block containing all // text and that of a narrow child before the child becomes a separate cluster. float maxAllowedDifferenceFromTextWidth; // Descendants of the cluster that are narrower than the block containing all text and must be // processed together. Vector narrowDescendants; }; static const Vector& formInputTags() { // Returns the tags for the form input elements. DEPRECATED_DEFINE_STATIC_LOCAL(Vector, formInputTags, ()); if (formInputTags.isEmpty()) { formInputTags.append(inputTag); formInputTags.append(buttonTag); formInputTags.append(selectTag); } return formInputTags; } TextAutosizer::TextAutosizer(Document* document) : m_document(document) { } TextAutosizer::~TextAutosizer() { } void TextAutosizer::recalculateMultipliers() { RenderObject* renderer = m_document->renderer(); while (renderer) { if (renderer->style() && renderer->style()->textAutosizingMultiplier() != 1) setMultiplier(renderer, 1); renderer = renderer->nextInPreOrder(); } } bool TextAutosizer::processSubtree(RenderObject* layoutRoot) { // FIXME: Text Autosizing should only be enabled when m_document->page()->mainFrame().view()->useFixedLayout() // is true, but for now it's useful to ignore this so that it can be tested on desktop. if (!m_document->settings() || !m_document->settings()->textAutosizingEnabled() || layoutRoot->view()->printing() || !m_document->page()) return false; Frame& mainFrame = m_document->page()->mainFrame(); TextAutosizingWindowInfo windowInfo; // Window area, in logical (density-independent) pixels. windowInfo.windowSize = m_document->settings()->textAutosizingWindowSizeOverride(); if (windowInfo.windowSize.isEmpty()) windowInfo.windowSize = mainFrame.view()->unscaledVisibleContentSize(ScrollableArea::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()) { if (!frame->view()->isInChildFrameWithFrameFlattening()) windowInfo.minLayoutSize = windowInfo.minLayoutSize.shrunkTo(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 = is(*layoutRoot) ? downcast(layoutRoot) : layoutRoot->containingBlock(); while (container && !isAutosizingContainer(container)) container = container->containingBlock(); RenderBlock* cluster = container; while (cluster && (!isAutosizingContainer(cluster) || !isIndependentDescendant(cluster))) cluster = cluster->containingBlock(); TextAutosizingClusterInfo clusterInfo(cluster); processCluster(clusterInfo, container, layoutRoot, windowInfo); 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(textWidth, logicalLayoutWidth); float multiplier = logicalClusterWidth / logicalWindowWidth; multiplier *= m_document->settings()->textAutosizingFontScaleFactor(); 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); Vector > narrowDescendantsGroups; getNarrowDescendantsGroupedByWidth(clusterInfo, narrowDescendantsGroups); for (size_t i = 0; i < narrowDescendantsGroups.size(); ++i) processCompositeCluster(narrowDescendantsGroups[i], windowInfo); } 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(); float multiplier = 1.0; if (clusterShouldBeAutosized(clusterInfo, textWidth)) multiplier = clusterMultiplier(clusterInfo.root->style()->writingMode(), windowInfo, textWidth); processClusterInternal(clusterInfo, container, subtreeRoot, windowInfo, multiplier); } void TextAutosizer::processCompositeCluster(Vector& 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(maxTextWidth, clusterInfo.blockContainingAllText->contentLogicalWidth()); } float multiplier = 1.0; if (compositeClusterShouldBeAutosized(clusterInfos, maxTextWidth)) multiplier = clusterMultiplier(clusterInfos[0].root->style()->writingMode(), 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::processContainer(float multiplier, RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo) { ASSERT(isAutosizingContainer(container)); float localMultiplier = containerShouldBeAutosized(container) ? multiplier: 1; RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(subtreeRoot, subtreeRoot); while (descendant) { if (is(*descendant)) { if (localMultiplier != 1 && descendant->style()->textAutosizingMultiplier() == 1) { setMultiplier(descendant, localMultiplier); setMultiplier(descendant->parent(), localMultiplier); // Parent does line spacing. } // FIXME: Increase list marker size proportionately. } else if (isAutosizingContainer(descendant)) { RenderBlock* descendantBlock = downcast(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 newStyle = RenderStyle::clone(renderer->style()); newStyle->setTextAutosizingMultiplier(multiplier); renderer->setStyle(newStyle.release()); } float TextAutosizer::computeAutosizedFontSize(float specifiedSize, float multiplier) { // Somewhat arbitrary "pleasant" font size. const float pleasantSize = 16; // Multiply fonts that the page author has specified to be larger than // pleasantSize by less and less, until huge fonts are not increased at all. // For specifiedSize between 0 and pleasantSize we directly apply the // multiplier; hence for specifiedSize == pleasantSize, computedSize will be // multiplier * pleasantSize. For greater specifiedSizes we want to // gradually fade out the multiplier, so for every 1px increase in // specifiedSize beyond pleasantSize we will only increase computedSize // by gradientAfterPleasantSize px until we meet the // computedSize = specifiedSize line, after which we stay on that line (so // then every 1px increase in specifiedSize increases computedSize by 1px). const float gradientAfterPleasantSize = 0.5; float computedSize; if (specifiedSize <= pleasantSize) computedSize = multiplier * specifiedSize; else { computedSize = multiplier * pleasantSize + gradientAfterPleasantSize * (specifiedSize - pleasantSize); if (computedSize < specifiedSize) computedSize = specifiedSize; } return computedSize; } 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. if (!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