/* * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "TextAutoSizing.h" #if ENABLE(IOS_TEXT_AUTOSIZING) #include "CSSFontSelector.h" #include "Document.h" #include "RenderListMarker.h" #include "RenderText.h" #include "StyleResolver.h" namespace WebCore { static PassRefPtr cloneRenderStyleWithState(const RenderStyle& currentStyle) { RefPtr newStyle = RenderStyle::clone(¤tStyle); if (currentStyle.lastChildState()) newStyle->setLastChildState(); if (currentStyle.firstChildState()) newStyle->setFirstChildState(); return newStyle.release(); } TextAutoSizingKey::TextAutoSizingKey() : m_style(0) , m_doc(0) { } TextAutoSizingKey::TextAutoSizingKey(RenderStyle* style, Document* doc) : m_style(style) , m_doc(doc) { ref(); } TextAutoSizingKey::TextAutoSizingKey(const TextAutoSizingKey& other) : m_style(other.m_style) , m_doc(other.m_doc) { ref(); } TextAutoSizingKey::~TextAutoSizingKey() { deref(); } TextAutoSizingKey& TextAutoSizingKey::operator=(const TextAutoSizingKey& other) { other.ref(); deref(); m_style = other.m_style; m_doc = other.m_doc; return *this; } void TextAutoSizingKey::ref() const { if (isValidStyle()) m_style->ref(); } void TextAutoSizingKey::deref() const { if (isValidStyle() && isValidDoc()) m_style->deref(); } int TextAutoSizingValue::numNodes() const { return m_autoSizedNodes.size(); } void TextAutoSizingValue::addNode(Node* node, float size) { ASSERT(node); downcast(*node->renderer()).setCandidateComputedTextSize(size); m_autoSizedNodes.add(node); } #define MAX_SCALE_INCREASE 1.7f bool TextAutoSizingValue::adjustNodeSizes() { bool objectsRemoved = false; // Remove stale nodes. Nodes may have had their renderers detached. We'll // also need to remove the style from the documents m_textAutoSizedNodes // collection. Return true indicates we need to do that removal. Vector > nodesForRemoval; HashSet >::iterator end = m_autoSizedNodes.end(); for (HashSet >::iterator i = m_autoSizedNodes.begin(); i != end; ++i) { RefPtr autoSizingNode = *i; RenderText* text = static_cast(autoSizingNode->renderer()); if (!text || !text->style().textSizeAdjust().isAuto() || !text->candidateComputedTextSize()) { // remove node. nodesForRemoval.append(autoSizingNode); objectsRemoved = true; } } unsigned count = nodesForRemoval.size(); for (unsigned i = 0; i < count; i++) m_autoSizedNodes.remove(nodesForRemoval[i]); // If we only have one piece of text with the style on the page don't // adjust it's size. if (m_autoSizedNodes.size() <= 1) return objectsRemoved; // Compute average size float cumulativeSize = 0; end = m_autoSizedNodes.end(); for (HashSet >::iterator i = m_autoSizedNodes.begin(); i != end; ++i) { RefPtr autoSizingNode = *i; RenderText* renderText = static_cast(autoSizingNode->renderer()); cumulativeSize += renderText->candidateComputedTextSize(); } float averageSize = roundf(cumulativeSize / m_autoSizedNodes.size()); // Adjust sizes bool firstPass = true; end = m_autoSizedNodes.end(); for (HashSet >::iterator i = m_autoSizedNodes.begin(); i != end; ++i) { const RefPtr& autoSizingNode = *i; RenderText* text = static_cast(autoSizingNode->renderer()); if (text && text->style().fontDescription().computedSize() != averageSize) { float specifiedSize = text->style().fontDescription().specifiedSize(); float scaleChange = averageSize / specifiedSize; if (scaleChange > MAX_SCALE_INCREASE && firstPass) { firstPass = false; averageSize = roundf(specifiedSize * MAX_SCALE_INCREASE); scaleChange = averageSize / specifiedSize; } RefPtr style = cloneRenderStyleWithState(text->style()); auto fontDescription = style->fontDescription(); fontDescription.setComputedSize(averageSize); style->setFontDescription(fontDescription); style->fontCascade().update(&autoSizingNode->document().fontSelector()); text->parent()->setStyle(style.releaseNonNull()); RenderElement* parentRenderer = text->parent(); if (parentRenderer->isAnonymousBlock()) parentRenderer = parentRenderer->parent(); // If we have a list we should resize ListMarkers separately. RenderObject* listMarkerRenderer = parentRenderer->firstChild(); if (listMarkerRenderer->isListMarker()) { RefPtr style = cloneRenderStyleWithState(listMarkerRenderer->style()); style->setFontDescription(fontDescription); style->fontCascade().update(&autoSizingNode->document().fontSelector()); downcast(*listMarkerRenderer).setStyle(style.releaseNonNull()); } // Resize the line height of the parent. const RenderStyle& parentStyle = parentRenderer->style(); Length lineHeightLength = parentStyle.specifiedLineHeight(); int specifiedLineHeight = 0; if (lineHeightLength.isPercent()) specifiedLineHeight = minimumValueForLength(lineHeightLength, fontDescription.specifiedSize()); else specifiedLineHeight = lineHeightLength.value(); int lineHeight = specifiedLineHeight * scaleChange; if (!lineHeightLength.isFixed() || lineHeightLength.value() != lineHeight) { RefPtr newParentStyle = cloneRenderStyleWithState(parentStyle); newParentStyle->setLineHeight(Length(lineHeight, Fixed)); newParentStyle->setSpecifiedLineHeight(lineHeightLength); newParentStyle->setFontDescription(fontDescription); newParentStyle->fontCascade().update(&autoSizingNode->document().fontSelector()); parentRenderer->setStyle(newParentStyle.releaseNonNull()); } } } return objectsRemoved; } void TextAutoSizingValue::reset() { HashSet >::iterator end = m_autoSizedNodes.end(); for (HashSet >::iterator i = m_autoSizedNodes.begin(); i != end; ++i) { const RefPtr& autoSizingNode = *i; RenderText* text = static_cast(autoSizingNode->renderer()); if (!text) continue; // Reset the font size back to the original specified size auto fontDescription = text->style().fontDescription(); float originalSize = fontDescription.specifiedSize(); if (fontDescription.computedSize() != originalSize) { fontDescription.setComputedSize(originalSize); RefPtr style = cloneRenderStyleWithState(text->style()); style->setFontDescription(fontDescription); style->fontCascade().update(&autoSizingNode->document().fontSelector()); text->parent()->setStyle(style.releaseNonNull()); } // Reset the line height of the parent. RenderElement* parentRenderer = text->parent(); if (!parentRenderer) continue; if (parentRenderer->isAnonymousBlock()) parentRenderer = parentRenderer->parent(); const RenderStyle& parentStyle = parentRenderer->style(); Length originalLineHeight = parentStyle.specifiedLineHeight(); if (originalLineHeight != parentStyle.lineHeight()) { RefPtr newParentStyle = cloneRenderStyleWithState(parentStyle); newParentStyle->setLineHeight(originalLineHeight); newParentStyle->setFontDescription(fontDescription); newParentStyle->fontCascade().update(&autoSizingNode->document().fontSelector()); parentRenderer->setStyle(newParentStyle.releaseNonNull()); } } } } // namespace WebCore #endif // ENABLE(IOS_TEXT_AUTOSIZING)