| /* |
| * Copyright (C) 2013 Google 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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 "core/layout/TextAutosizer.h" |
| |
| #include "core/dom/Document.h" |
| #include "core/frame/FrameHost.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/Settings.h" |
| #include "core/frame/VisualViewport.h" |
| #include "core/html/HTMLTextAreaElement.h" |
| #include "core/layout/LayoutBlock.h" |
| #include "core/layout/LayoutInline.h" |
| #include "core/layout/LayoutListItem.h" |
| #include "core/layout/LayoutListMarker.h" |
| #include "core/layout/LayoutTable.h" |
| #include "core/layout/LayoutTableCell.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/api/LayoutAPIShim.h" |
| #include "core/layout/api/LayoutViewItem.h" |
| #include "core/page/Page.h" |
| #include "wtf/PtrUtil.h" |
| #include <memory> |
| |
| #ifdef AUTOSIZING_DOM_DEBUG_INFO |
| #include "core/dom/ExecutionContextTask.h" |
| #endif |
| |
| namespace blink { |
| |
| #ifdef AUTOSIZING_DOM_DEBUG_INFO |
| class WriteDebugInfoTask : public ExecutionContextTask { |
| public: |
| WriteDebugInfoTask(Element* element, AtomicString value) |
| : m_element(element), m_value(value) {} |
| |
| virtual void performTask(ExecutionContext*) { |
| m_element->setAttribute("data-autosizing", m_value, ASSERT_NO_EXCEPTION); |
| } |
| |
| private: |
| Persistent<Element> m_element; |
| AtomicString m_value; |
| }; |
| |
| static void writeDebugInfo(LayoutObject* layoutObject, |
| const AtomicString& output) { |
| Node* node = layoutObject->node(); |
| if (!node) |
| return; |
| if (node->isDocumentNode()) |
| node = toDocument(node)->documentElement(); |
| if (!node->isElementNode()) |
| return; |
| node->document().postTask(BLINK_FROM_HERE, wrapUnique(new WriteDebugInfoTask( |
| toElement(node), output))); |
| } |
| |
| void TextAutosizer::writeClusterDebugInfo(Cluster* cluster) { |
| String explanation = ""; |
| if (cluster->m_flags & SUPPRESSING) { |
| explanation = "[suppressed]"; |
| } else if (!(cluster->m_flags & (INDEPENDENT | WIDER_OR_NARROWER))) { |
| explanation = "[inherited]"; |
| } else if (cluster->m_supercluster) { |
| explanation = "[supercluster]"; |
| } else if (!clusterHasEnoughTextToAutosize(cluster)) { |
| explanation = "[insufficient-text]"; |
| } else { |
| const LayoutBlock* widthProvider = clusterWidthProvider(cluster->m_root); |
| if (cluster->m_hasTableAncestor && |
| cluster->m_multiplier < multiplierFromBlock(widthProvider)) { |
| explanation = "[table-ancestor-limited]"; |
| } else { |
| explanation = |
| String::format("[from width %d of %s]", |
| static_cast<int>(widthFromBlock(widthProvider)), |
| widthProvider->debugName().utf8().data()); |
| } |
| } |
| String pageInfo = ""; |
| if (cluster->m_root->isLayoutView()) { |
| pageInfo = |
| String::format("; pageinfo: afsf %f * dsa %f * (lw %d / fw %d)", |
| m_pageInfo.m_accessibilityFontScaleFactor, |
| m_pageInfo.m_deviceScaleAdjustment, |
| m_pageInfo.m_layoutWidth, m_pageInfo.m_frameWidth); |
| } |
| float multiplier = |
| cluster->m_flags & SUPPRESSING ? 1.0 : cluster->m_multiplier; |
| writeDebugInfo(const_cast<LayoutBlock*>(cluster->m_root), |
| AtomicString(String::format("cluster: %f %s%s", multiplier, |
| explanation.utf8().data(), |
| pageInfo.utf8().data()))); |
| } |
| #endif |
| |
| static const LayoutObject* parentElementLayoutObject( |
| const LayoutObject* layoutObject) { |
| // At style recalc, the layoutObject's parent may not be attached, |
| // so we need to obtain this from the DOM tree. |
| const Node* node = layoutObject->node(); |
| if (!node) |
| return nullptr; |
| |
| // FIXME: This should be using LayoutTreeBuilderTraversal::parent(). |
| if (Element* parent = node->parentElement()) |
| return parent->layoutObject(); |
| return nullptr; |
| } |
| |
| static bool isNonTextAreaFormControl(const LayoutObject* layoutObject) { |
| const Node* node = layoutObject ? layoutObject->node() : nullptr; |
| if (!node || !node->isElementNode()) |
| return false; |
| const Element* element = toElement(node); |
| |
| return (element->isFormControlElement() && !isHTMLTextAreaElement(element)); |
| } |
| |
| static bool isPotentialClusterRoot(const LayoutObject* layoutObject) { |
| // "Potential cluster roots" are the smallest unit for which we can |
| // enable/disable text autosizing. |
| // - Must have children. |
| // An exception is made for LayoutView which should create a root to |
| // maintain consistency with documents that have no child nodes but may |
| // still have LayoutObject children. |
| // - 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 normal list items, as items in the same list should look |
| // consistent, unless they are floating or position:absolute/fixed. |
| Node* node = layoutObject->generatingNode(); |
| if (node && !node->hasChildren() && !layoutObject->isLayoutView()) |
| return false; |
| if (!layoutObject->isLayoutBlock()) |
| return false; |
| if (layoutObject->isInline() && |
| !layoutObject->style()->isDisplayReplacedType()) |
| return false; |
| if (layoutObject->isListItem()) |
| return (layoutObject->isFloating() || |
| layoutObject->isOutOfFlowPositioned()); |
| |
| return true; |
| } |
| |
| static bool isIndependentDescendant(const LayoutBlock* layoutObject) { |
| ASSERT(isPotentialClusterRoot(layoutObject)); |
| |
| LayoutBlock* containingBlock = layoutObject->containingBlock(); |
| return layoutObject->isLayoutView() || layoutObject->isFloating() || |
| layoutObject->isOutOfFlowPositioned() || layoutObject->isTableCell() || |
| layoutObject->isTableCaption() || |
| layoutObject->isFlexibleBoxIncludingDeprecated() || |
| (containingBlock && |
| containingBlock->isHorizontalWritingMode() != |
| layoutObject->isHorizontalWritingMode()) || |
| layoutObject->style()->isDisplayReplacedType() || |
| layoutObject->isTextArea() || |
| layoutObject->style()->userModify() != READ_ONLY; |
| } |
| |
| static bool blockIsRowOfLinks(const LayoutBlock* block) { |
| // A "row of links" is a block for which: |
| // 1. It does not contain non-link text elements longer than 3 characters |
| // 2. It contains a minimum of 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; |
| LayoutObject* layoutObject = block->firstChild(); |
| float matchingFontSize = -1; |
| |
| while (layoutObject) { |
| if (!isPotentialClusterRoot(layoutObject)) { |
| if (layoutObject->isText() && |
| toLayoutText(layoutObject)->text().stripWhiteSpace().length() > 3) |
| return false; |
| if (!layoutObject->isInline() || layoutObject->isBR()) |
| return false; |
| } |
| if (layoutObject->style()->isLink()) { |
| linkCount++; |
| if (matchingFontSize < 0) |
| matchingFontSize = layoutObject->style()->specifiedFontSize(); |
| else if (matchingFontSize != layoutObject->style()->specifiedFontSize()) |
| return false; |
| |
| // Skip traversing descendants of the link. |
| layoutObject = layoutObject->nextInPreOrderAfterChildren(block); |
| continue; |
| } |
| layoutObject = layoutObject->nextInPreOrder(block); |
| } |
| |
| return (linkCount >= 3); |
| } |
| |
| static bool blockHeightConstrained(const LayoutBlock* block) { |
| // 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 (; block; block = block->containingBlock()) { |
| const ComputedStyle& style = block->styleRef(); |
| if (style.overflowY() >= OverflowScroll) |
| return false; |
| if (style.height().isSpecified() || style.maxHeight().isSpecified() || |
| block->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 !block->isDocumentElement() && !block->isBody() && |
| !block->isLayoutView(); |
| } |
| if (block->isFloating()) |
| return false; |
| } |
| return false; |
| } |
| |
| static bool blockOrImmediateChildrenAreFormControls(const LayoutBlock* block) { |
| if (isNonTextAreaFormControl(block)) |
| return true; |
| const LayoutObject* layoutObject = block->firstChild(); |
| while (layoutObject) { |
| if (isNonTextAreaFormControl(layoutObject)) |
| return true; |
| layoutObject = layoutObject->nextSibling(); |
| } |
| |
| return false; |
| } |
| |
| // Some blocks are not autosized even if their parent cluster wants them to. |
| static bool blockSuppressesAutosizing(const LayoutBlock* block) { |
| if (blockOrImmediateChildrenAreFormControls(block)) |
| return true; |
| |
| if (blockIsRowOfLinks(block)) |
| return true; |
| |
| // Don't autosize block-level text that can't wrap (as it's likely to |
| // expand sideways and break the page's layout). |
| if (!block->style()->autoWrap()) |
| return true; |
| |
| if (blockHeightConstrained(block)) |
| return true; |
| |
| return false; |
| } |
| |
| static bool hasExplicitWidth(const LayoutBlock* block) { |
| // FIXME: This heuristic may need to be expanded to other ways a block can be |
| // wider or narrower than its parent containing block. |
| return block->style() && block->style()->width().isSpecified(); |
| } |
| |
| TextAutosizer::TextAutosizer(const Document* document) |
| : m_document(document), |
| m_firstBlockToBeginLayout(nullptr), |
| #if ENABLE(ASSERT) |
| m_blocksThatHaveBegunLayout(), |
| #endif |
| m_superclusters(), |
| m_clusterStack(), |
| m_fingerprintMapper(), |
| m_pageInfo(), |
| m_updatePageInfoDeferred(false) { |
| } |
| |
| TextAutosizer::~TextAutosizer() {} |
| |
| void TextAutosizer::record(const LayoutBlock* block) { |
| if (!m_pageInfo.m_settingEnabled) |
| return; |
| |
| ASSERT(!m_blocksThatHaveBegunLayout.contains(block)); |
| |
| if (!classifyBlock(block, INDEPENDENT | EXPLICIT_WIDTH)) |
| return; |
| |
| if (Fingerprint fingerprint = computeFingerprint(block)) |
| m_fingerprintMapper.addTentativeClusterRoot(block, fingerprint); |
| } |
| |
| void TextAutosizer::destroy(const LayoutBlock* block) { |
| if (!m_pageInfo.m_settingEnabled && !m_fingerprintMapper.hasFingerprints()) |
| return; |
| |
| ASSERT(!m_blocksThatHaveBegunLayout.contains(block)); |
| |
| if (m_fingerprintMapper.remove(block) && m_firstBlockToBeginLayout) { |
| // LayoutBlock with a fingerprint was destroyed during layout. |
| // Clear the cluster stack and the supercluster map to avoid stale pointers. |
| // Speculative fix for http://crbug.com/369485. |
| m_firstBlockToBeginLayout = nullptr; |
| m_clusterStack.clear(); |
| m_superclusters.clear(); |
| } |
| } |
| |
| TextAutosizer::BeginLayoutBehavior TextAutosizer::prepareForLayout( |
| const LayoutBlock* block) { |
| #if ENABLE(ASSERT) |
| m_blocksThatHaveBegunLayout.add(block); |
| #endif |
| |
| if (!m_firstBlockToBeginLayout) { |
| m_firstBlockToBeginLayout = block; |
| prepareClusterStack(block->parent()); |
| } else if (block == currentCluster()->m_root) { |
| // Ignore beginLayout on the same block twice. |
| // This can happen with paginated overflow. |
| return StopLayout; |
| } |
| |
| return ContinueLayout; |
| } |
| |
| void TextAutosizer::prepareClusterStack(const LayoutObject* layoutObject) { |
| if (!layoutObject) |
| return; |
| prepareClusterStack(layoutObject->parent()); |
| |
| if (layoutObject->isLayoutBlock()) { |
| const LayoutBlock* block = toLayoutBlock(layoutObject); |
| #if ENABLE(ASSERT) |
| m_blocksThatHaveBegunLayout.add(block); |
| #endif |
| if (Cluster* cluster = maybeCreateCluster(block)) |
| m_clusterStack.append(wrapUnique(cluster)); |
| } |
| } |
| |
| void TextAutosizer::beginLayout(LayoutBlock* block, |
| SubtreeLayoutScope* layouter) { |
| ASSERT(shouldHandleLayout()); |
| |
| if (prepareForLayout(block) == StopLayout) |
| return; |
| |
| ASSERT(!m_clusterStack.isEmpty() || block->isLayoutView()); |
| |
| if (Cluster* cluster = maybeCreateCluster(block)) |
| m_clusterStack.append(wrapUnique(cluster)); |
| |
| ASSERT(!m_clusterStack.isEmpty()); |
| |
| // Cells in auto-layout tables are handled separately by inflateAutoTable. |
| bool isAutoTableCell = |
| block->isTableCell() && |
| !toLayoutTableCell(block)->table()->style()->isFixedTableLayout(); |
| if (!isAutoTableCell && !m_clusterStack.isEmpty()) |
| inflate(block, layouter); |
| } |
| |
| void TextAutosizer::inflateAutoTable(LayoutTable* table) { |
| ASSERT(table); |
| ASSERT(!table->style()->isFixedTableLayout()); |
| ASSERT(table->containingBlock()); |
| |
| Cluster* cluster = currentCluster(); |
| if (cluster->m_root != table) |
| return; |
| |
| // Pre-inflate cells that have enough text so that their inflated preferred |
| // widths will be used for column sizing. |
| for (LayoutObject* section = table->firstChild(); section; |
| section = section->nextSibling()) { |
| if (!section->isTableSection()) |
| continue; |
| for (LayoutTableRow* row = toLayoutTableSection(section)->firstRow(); row; |
| row = row->nextRow()) { |
| for (LayoutTableCell* cell = row->firstCell(); cell; |
| cell = cell->nextCell()) { |
| if (!cell->needsLayout()) |
| continue; |
| |
| beginLayout(cell, nullptr); |
| inflate(cell, nullptr, DescendToInnerBlocks); |
| endLayout(cell); |
| } |
| } |
| } |
| } |
| |
| void TextAutosizer::endLayout(LayoutBlock* block) { |
| ASSERT(shouldHandleLayout()); |
| |
| if (block == m_firstBlockToBeginLayout) { |
| m_firstBlockToBeginLayout = nullptr; |
| m_clusterStack.clear(); |
| m_superclusters.clear(); |
| m_stylesRetainedDuringLayout.clear(); |
| #if ENABLE(ASSERT) |
| m_blocksThatHaveBegunLayout.clear(); |
| #endif |
| // Tables can create two layout scopes for the same block so the isEmpty |
| // check below is needed to guard against endLayout being called twice. |
| } else if (!m_clusterStack.isEmpty() && currentCluster()->m_root == block) { |
| m_clusterStack.removeLast(); |
| } |
| } |
| |
| float TextAutosizer::inflate(LayoutObject* parent, |
| SubtreeLayoutScope* layouter, |
| InflateBehavior behavior, |
| float multiplier) { |
| Cluster* cluster = currentCluster(); |
| bool hasTextChild = false; |
| |
| LayoutObject* child = nullptr; |
| if (parent->isLayoutBlock() && |
| (parent->childrenInline() || behavior == DescendToInnerBlocks)) |
| child = toLayoutBlock(parent)->firstChild(); |
| else if (parent->isLayoutInline()) |
| child = toLayoutInline(parent)->firstChild(); |
| |
| while (child) { |
| if (child->isText()) { |
| hasTextChild = true; |
| // We only calculate this multiplier on-demand to ensure the parent block |
| // of this text has entered layout. |
| if (!multiplier) |
| multiplier = |
| cluster->m_flags & SUPPRESSING ? 1.0f : clusterMultiplier(cluster); |
| applyMultiplier(child, multiplier, layouter); |
| |
| // FIXME: Investigate why MarkOnlyThis is sufficient. |
| if (parent->isLayoutInline()) |
| child->setPreferredLogicalWidthsDirty(MarkOnlyThis); |
| } else if (child->isLayoutInline()) { |
| multiplier = inflate(child, layouter, behavior, multiplier); |
| } else if (child->isLayoutBlock() && behavior == DescendToInnerBlocks && |
| !classifyBlock(child, |
| INDEPENDENT | EXPLICIT_WIDTH | SUPPRESSING)) { |
| multiplier = inflate(child, layouter, behavior, multiplier); |
| } |
| child = child->nextSibling(); |
| } |
| |
| if (hasTextChild) { |
| applyMultiplier(parent, multiplier, |
| layouter); // Parent handles line spacing. |
| } else if (!parent->isListItem()) { |
| // For consistency, a block with no immediate text child should always have |
| // a multiplier of 1. |
| applyMultiplier(parent, 1, layouter); |
| } |
| |
| if (parent->isListItem()) { |
| float multiplier = clusterMultiplier(cluster); |
| applyMultiplier(parent, multiplier, layouter); |
| |
| // The list item has to be treated special because we can have a tree such |
| // that you have a list item for a form inside it. The list marker then ends |
| // up inside the form and when we try to get the clusterMultiplier we have |
| // the wrong cluster root to work from and get the wrong value. |
| LayoutListItem* item = toLayoutListItem(parent); |
| if (LayoutListMarker* marker = item->marker()) { |
| applyMultiplier(marker, multiplier, layouter); |
| marker->setPreferredLogicalWidthsDirty(MarkOnlyThis); |
| } |
| } |
| |
| if (m_pageInfo.m_hasAutosized) |
| UseCounter::count(*m_document, UseCounter::TextAutosizing); |
| |
| return multiplier; |
| } |
| |
| bool TextAutosizer::shouldHandleLayout() const { |
| return m_pageInfo.m_settingEnabled && m_pageInfo.m_pageNeedsAutosizing && |
| !m_updatePageInfoDeferred; |
| } |
| |
| bool TextAutosizer::pageNeedsAutosizing() const { |
| return m_pageInfo.m_pageNeedsAutosizing; |
| } |
| |
| void TextAutosizer::updatePageInfoInAllFrames() { |
| ASSERT(!m_document->frame() || m_document->frame()->isMainFrame()); |
| |
| for (Frame* frame = m_document->frame(); frame; |
| frame = frame->tree().traverseNext()) { |
| if (!frame->isLocalFrame()) |
| continue; |
| |
| Document* document = toLocalFrame(frame)->document(); |
| // If document is being detached, skip updatePageInfo. |
| if (!document || !document->isActive()) |
| continue; |
| if (TextAutosizer* textAutosizer = document->textAutosizer()) |
| textAutosizer->updatePageInfo(); |
| } |
| } |
| |
| void TextAutosizer::updatePageInfo() { |
| if (m_updatePageInfoDeferred || !m_document->page() || |
| !m_document->settings()) |
| return; |
| |
| PageInfo previousPageInfo(m_pageInfo); |
| m_pageInfo.m_settingEnabled = m_document->settings()->textAutosizingEnabled(); |
| |
| if (!m_pageInfo.m_settingEnabled || m_document->printing()) { |
| m_pageInfo.m_pageNeedsAutosizing = false; |
| } else { |
| LayoutViewItem layoutViewItem = m_document->layoutViewItem(); |
| bool horizontalWritingMode = |
| isHorizontalWritingMode(layoutViewItem.style()->getWritingMode()); |
| |
| // FIXME: With out-of-process iframes, the top frame can be remote and |
| // doesn't have sizing information. Just return if this is the case. |
| Frame* frame = m_document->frame()->tree().top(); |
| if (frame->isRemoteFrame()) |
| return; |
| |
| LocalFrame* mainFrame = toLocalFrame(frame); |
| IntSize frameSize = |
| m_document->settings()->textAutosizingWindowSizeOverride(); |
| if (frameSize.isEmpty()) |
| frameSize = windowSize(); |
| |
| m_pageInfo.m_frameWidth = |
| horizontalWritingMode ? frameSize.width() : frameSize.height(); |
| |
| IntSize layoutSize = mainFrame->view()->layoutSize(); |
| m_pageInfo.m_layoutWidth = |
| horizontalWritingMode ? layoutSize.width() : layoutSize.height(); |
| |
| // TODO(pdr): Accessibility should be moved out of the text autosizer. See: |
| // crbug.com/645717. |
| m_pageInfo.m_accessibilityFontScaleFactor = |
| m_document->settings()->accessibilityFontScaleFactor(); |
| |
| // If the page has a meta viewport or @viewport, don't apply the device |
| // scale adjustment. |
| if (!mainFrame->document()->viewportDescription().isSpecifiedByAuthor()) |
| m_pageInfo.m_deviceScaleAdjustment = |
| m_document->settings()->deviceScaleAdjustment(); |
| else |
| m_pageInfo.m_deviceScaleAdjustment = 1.0f; |
| |
| // TODO(pdr): pageNeedsAutosizing should take into account whether |
| // text-size-adjust is used anywhere on the page because that also needs to |
| // trigger autosizing. See: crbug.com/646237. |
| m_pageInfo.m_pageNeedsAutosizing = |
| !!m_pageInfo.m_frameWidth && |
| (m_pageInfo.m_accessibilityFontScaleFactor * |
| m_pageInfo.m_deviceScaleAdjustment * |
| (static_cast<float>(m_pageInfo.m_layoutWidth) / |
| m_pageInfo.m_frameWidth) > |
| 1.0f); |
| } |
| |
| if (m_pageInfo.m_pageNeedsAutosizing) { |
| // If page info has changed, multipliers may have changed. Force a layout to |
| // recompute them. |
| if (m_pageInfo.m_frameWidth != previousPageInfo.m_frameWidth || |
| m_pageInfo.m_layoutWidth != previousPageInfo.m_layoutWidth || |
| m_pageInfo.m_accessibilityFontScaleFactor != |
| previousPageInfo.m_accessibilityFontScaleFactor || |
| m_pageInfo.m_deviceScaleAdjustment != |
| previousPageInfo.m_deviceScaleAdjustment || |
| m_pageInfo.m_settingEnabled != previousPageInfo.m_settingEnabled) |
| setAllTextNeedsLayout(); |
| } else if (previousPageInfo.m_hasAutosized) { |
| // If we are no longer autosizing the page, we won't do anything during the |
| // next layout. Set all the multipliers back to 1 now. |
| resetMultipliers(); |
| m_pageInfo.m_hasAutosized = false; |
| } |
| } |
| |
| IntSize TextAutosizer::windowSize() const { |
| Page* page = m_document->page(); |
| ASSERT(page); |
| return page->frameHost().visualViewport().size(); |
| } |
| |
| void TextAutosizer::resetMultipliers() { |
| LayoutObject* layoutObject = |
| LayoutAPIShim::layoutObjectFrom(m_document->layoutViewItem()); |
| while (layoutObject) { |
| if (const ComputedStyle* style = layoutObject->style()) { |
| if (style->textAutosizingMultiplier() != 1) |
| applyMultiplier(layoutObject, 1, nullptr, LayoutNeeded); |
| } |
| layoutObject = layoutObject->nextInPreOrder(); |
| } |
| } |
| |
| void TextAutosizer::setAllTextNeedsLayout() { |
| LayoutItem layoutItem = m_document->layoutViewItem(); |
| while (!layoutItem.isNull()) { |
| if (layoutItem.isText()) |
| layoutItem.setNeedsLayoutAndFullPaintInvalidation( |
| LayoutInvalidationReason::TextAutosizing); |
| layoutItem = layoutItem.nextInPreOrder(); |
| } |
| } |
| |
| TextAutosizer::BlockFlags TextAutosizer::classifyBlock( |
| const LayoutObject* layoutObject, |
| BlockFlags mask) const { |
| if (!layoutObject->isLayoutBlock()) |
| return 0; |
| |
| const LayoutBlock* block = toLayoutBlock(layoutObject); |
| BlockFlags flags = 0; |
| |
| if (isPotentialClusterRoot(block)) { |
| if (mask & POTENTIAL_ROOT) |
| flags |= POTENTIAL_ROOT; |
| |
| if ((mask & INDEPENDENT) && |
| (isIndependentDescendant(block) || block->isTable())) |
| flags |= INDEPENDENT; |
| |
| if ((mask & EXPLICIT_WIDTH) && hasExplicitWidth(block)) |
| flags |= EXPLICIT_WIDTH; |
| |
| if ((mask & SUPPRESSING) && blockSuppressesAutosizing(block)) |
| flags |= SUPPRESSING; |
| } |
| return flags; |
| } |
| |
| bool TextAutosizer::clusterWouldHaveEnoughTextToAutosize( |
| const LayoutBlock* root, |
| const LayoutBlock* widthProvider) { |
| Cluster hypotheticalCluster(root, classifyBlock(root), nullptr); |
| return clusterHasEnoughTextToAutosize(&hypotheticalCluster, widthProvider); |
| } |
| |
| bool TextAutosizer::clusterHasEnoughTextToAutosize( |
| Cluster* cluster, |
| const LayoutBlock* widthProvider) { |
| if (cluster->m_hasEnoughTextToAutosize != UnknownAmountOfText) |
| return cluster->m_hasEnoughTextToAutosize == HasEnoughText; |
| |
| const LayoutBlock* root = cluster->m_root; |
| if (!widthProvider) |
| widthProvider = clusterWidthProvider(root); |
| |
| // TextAreas and user-modifiable areas get a free pass to autosize regardless |
| // of text content. |
| if (root->isTextArea() || |
| (root->style() && root->style()->userModify() != READ_ONLY)) { |
| cluster->m_hasEnoughTextToAutosize = HasEnoughText; |
| return true; |
| } |
| |
| if (cluster->m_flags & SUPPRESSING) { |
| cluster->m_hasEnoughTextToAutosize = NotEnoughText; |
| return false; |
| } |
| |
| // 4 lines of text is considered enough to autosize. |
| float minimumTextLengthToAutosize = widthFromBlock(widthProvider) * 4; |
| |
| float length = 0; |
| LayoutObject* descendant = root->firstChild(); |
| while (descendant) { |
| if (descendant->isLayoutBlock()) { |
| if (classifyBlock(descendant, INDEPENDENT | SUPPRESSING)) { |
| descendant = descendant->nextInPreOrderAfterChildren(root); |
| continue; |
| } |
| } else if (descendant->isText()) { |
| // Note: Using text().stripWhiteSpace().length() instead of |
| // resolvedTextLength() because the lineboxes will not be built until |
| // layout. These values can be different. |
| // Note: This is an approximation assuming each character is 1em wide. |
| length += toLayoutText(descendant)->text().stripWhiteSpace().length() * |
| descendant->style()->specifiedFontSize(); |
| |
| if (length >= minimumTextLengthToAutosize) { |
| cluster->m_hasEnoughTextToAutosize = HasEnoughText; |
| return true; |
| } |
| } |
| descendant = descendant->nextInPreOrder(root); |
| } |
| |
| cluster->m_hasEnoughTextToAutosize = NotEnoughText; |
| return false; |
| } |
| |
| TextAutosizer::Fingerprint TextAutosizer::getFingerprint( |
| const LayoutObject* layoutObject) { |
| Fingerprint result = m_fingerprintMapper.get(layoutObject); |
| if (!result) { |
| result = computeFingerprint(layoutObject); |
| m_fingerprintMapper.add(layoutObject, result); |
| } |
| return result; |
| } |
| |
| TextAutosizer::Fingerprint TextAutosizer::computeFingerprint( |
| const LayoutObject* layoutObject) { |
| Node* node = layoutObject->generatingNode(); |
| if (!node || !node->isElementNode()) |
| return 0; |
| |
| FingerprintSourceData data; |
| if (const LayoutObject* parent = parentElementLayoutObject(layoutObject)) |
| data.m_parentHash = getFingerprint(parent); |
| |
| data.m_qualifiedNameHash = |
| QualifiedNameHash::hash(toElement(node)->tagQName()); |
| |
| if (const ComputedStyle* style = layoutObject->style()) { |
| data.m_packedStyleProperties = style->direction(); |
| data.m_packedStyleProperties |= (style->position() << 1); |
| data.m_packedStyleProperties |= |
| (static_cast<unsigned>(style->floating()) << 4); |
| data.m_packedStyleProperties |= |
| (static_cast<unsigned>(style->display()) << 6); |
| data.m_packedStyleProperties |= (style->width().type() << 11); |
| // packedStyleProperties effectively using 15 bits now. |
| |
| // consider for adding: writing mode, padding. |
| |
| data.m_width = style->width().getFloatValue(); |
| } |
| |
| // Use nodeIndex as a rough approximation of column number |
| // (it's too early to call LayoutTableCell::col). |
| // FIXME: account for colspan |
| if (layoutObject->isTableCell()) |
| data.m_column = layoutObject->node()->nodeIndex(); |
| |
| return StringHasher::computeHash<UChar>( |
| static_cast<const UChar*>(static_cast<const void*>(&data)), |
| sizeof data / sizeof(UChar)); |
| } |
| |
| TextAutosizer::Cluster* TextAutosizer::maybeCreateCluster( |
| const LayoutBlock* block) { |
| BlockFlags flags = classifyBlock(block); |
| if (!(flags & POTENTIAL_ROOT)) |
| return nullptr; |
| |
| Cluster* parentCluster = |
| m_clusterStack.isEmpty() ? nullptr : currentCluster(); |
| ASSERT(parentCluster || block->isLayoutView()); |
| |
| // If a non-independent block would not alter the SUPPRESSING flag, it doesn't |
| // need to be a cluster. |
| bool parentSuppresses = |
| parentCluster && (parentCluster->m_flags & SUPPRESSING); |
| if (!(flags & INDEPENDENT) && !(flags & EXPLICIT_WIDTH) && |
| !!(flags & SUPPRESSING) == parentSuppresses) |
| return nullptr; |
| |
| Cluster* cluster = |
| new Cluster(block, flags, parentCluster, getSupercluster(block)); |
| #ifdef AUTOSIZING_DOM_DEBUG_INFO |
| // Non-SUPPRESSING clusters are annotated in clusterMultiplier. |
| if (flags & SUPPRESSING) |
| writeClusterDebugInfo(cluster); |
| #endif |
| return cluster; |
| } |
| |
| TextAutosizer::Supercluster* TextAutosizer::getSupercluster( |
| const LayoutBlock* block) { |
| Fingerprint fingerprint = m_fingerprintMapper.get(block); |
| if (!fingerprint) |
| return nullptr; |
| |
| BlockSet* roots = m_fingerprintMapper.getTentativeClusterRoots(fingerprint); |
| if (!roots || roots->size() < 2 || !roots->contains(block)) |
| return nullptr; |
| |
| SuperclusterMap::AddResult addResult = |
| m_superclusters.add(fingerprint, std::unique_ptr<Supercluster>()); |
| if (!addResult.isNewEntry) |
| return addResult.storedValue->value.get(); |
| |
| Supercluster* supercluster = new Supercluster(roots); |
| addResult.storedValue->value = wrapUnique(supercluster); |
| return supercluster; |
| } |
| |
| float TextAutosizer::clusterMultiplier(Cluster* cluster) { |
| if (cluster->m_multiplier) |
| return cluster->m_multiplier; |
| |
| // FIXME: why does isWiderOrNarrowerDescendant crash on independent clusters? |
| if (!(cluster->m_flags & INDEPENDENT) && isWiderOrNarrowerDescendant(cluster)) |
| cluster->m_flags |= WIDER_OR_NARROWER; |
| |
| if (cluster->m_flags & (INDEPENDENT | WIDER_OR_NARROWER)) { |
| if (cluster->m_supercluster) |
| cluster->m_multiplier = superclusterMultiplier(cluster); |
| else if (clusterHasEnoughTextToAutosize(cluster)) |
| cluster->m_multiplier = |
| multiplierFromBlock(clusterWidthProvider(cluster->m_root)); |
| else |
| cluster->m_multiplier = 1.0f; |
| } else { |
| cluster->m_multiplier = |
| cluster->m_parent ? clusterMultiplier(cluster->m_parent) : 1.0f; |
| } |
| |
| #ifdef AUTOSIZING_DOM_DEBUG_INFO |
| writeClusterDebugInfo(cluster); |
| #endif |
| |
| ASSERT(cluster->m_multiplier); |
| return cluster->m_multiplier; |
| } |
| |
| bool TextAutosizer::superclusterHasEnoughTextToAutosize( |
| Supercluster* supercluster, |
| const LayoutBlock* widthProvider) { |
| if (supercluster->m_hasEnoughTextToAutosize != UnknownAmountOfText) |
| return supercluster->m_hasEnoughTextToAutosize == HasEnoughText; |
| |
| for (auto* root : *supercluster->m_roots) { |
| if (clusterWouldHaveEnoughTextToAutosize(root, widthProvider)) { |
| supercluster->m_hasEnoughTextToAutosize = HasEnoughText; |
| return true; |
| } |
| } |
| supercluster->m_hasEnoughTextToAutosize = NotEnoughText; |
| return false; |
| } |
| |
| float TextAutosizer::superclusterMultiplier(Cluster* cluster) { |
| Supercluster* supercluster = cluster->m_supercluster; |
| if (!supercluster->m_multiplier) { |
| const LayoutBlock* widthProvider = |
| maxClusterWidthProvider(cluster->m_supercluster, cluster->m_root); |
| supercluster->m_multiplier = |
| superclusterHasEnoughTextToAutosize(supercluster, widthProvider) |
| ? multiplierFromBlock(widthProvider) |
| : 1.0f; |
| } |
| ASSERT(supercluster->m_multiplier); |
| return supercluster->m_multiplier; |
| } |
| |
| const LayoutBlock* TextAutosizer::clusterWidthProvider( |
| const LayoutBlock* root) const { |
| if (root->isTable() || root->isTableCell()) |
| return root; |
| |
| return deepestBlockContainingAllText(root); |
| } |
| |
| const LayoutBlock* TextAutosizer::maxClusterWidthProvider( |
| const Supercluster* supercluster, |
| const LayoutBlock* currentRoot) const { |
| const LayoutBlock* result = clusterWidthProvider(currentRoot); |
| float maxWidth = widthFromBlock(result); |
| |
| const BlockSet* roots = supercluster->m_roots; |
| for (const auto* root : *roots) { |
| const LayoutBlock* widthProvider = clusterWidthProvider(root); |
| if (widthProvider->needsLayout()) |
| continue; |
| float width = widthFromBlock(widthProvider); |
| if (width > maxWidth) { |
| maxWidth = width; |
| result = widthProvider; |
| } |
| } |
| RELEASE_ASSERT(result); |
| return result; |
| } |
| |
| float TextAutosizer::widthFromBlock(const LayoutBlock* block) const { |
| RELEASE_ASSERT(block); |
| RELEASE_ASSERT(block->style()); |
| |
| if (!(block->isTable() || block->isTableCell() || block->isListItem())) |
| return block->contentLogicalWidth().toFloat(); |
| |
| if (!block->containingBlock()) |
| return 0; |
| |
| // Tables may be inflated before computing their preferred widths. Try several |
| // methods to obtain a width, and fall back on a containing block's width. |
| for (; block; block = block->containingBlock()) { |
| float width; |
| Length specifiedWidth = |
| block->isTableCell() |
| ? toLayoutTableCell(block)->styleOrColLogicalWidth() |
| : block->style()->logicalWidth(); |
| if (specifiedWidth.isFixed()) { |
| if ((width = specifiedWidth.value()) > 0) |
| return width; |
| } |
| if (specifiedWidth.isPercentOrCalc()) { |
| if (float containerWidth = |
| block->containingBlock()->contentLogicalWidth().toFloat()) { |
| if ((width = floatValueForLength(specifiedWidth, containerWidth)) > 0) |
| return width; |
| } |
| } |
| if ((width = block->contentLogicalWidth().toFloat()) > 0) |
| return width; |
| } |
| return 0; |
| } |
| |
| float TextAutosizer::multiplierFromBlock(const LayoutBlock* block) { |
| // If block->needsLayout() is false, it does not need to be in |
| // m_blocksThatHaveBegunLayout. This can happen during layout of a positioned |
| // object if the cluster's DBCAT is deeper than the positioned object's |
| // containing block, and wasn't marked as needing layout. |
| ASSERT(m_blocksThatHaveBegunLayout.contains(block) || !block->needsLayout()); |
| |
| // Block width, in CSS pixels. |
| float blockWidth = widthFromBlock(block); |
| float layoutWidth = |
| std::min(blockWidth, static_cast<float>(m_pageInfo.m_layoutWidth)); |
| float multiplier = |
| m_pageInfo.m_frameWidth ? layoutWidth / m_pageInfo.m_frameWidth : 1.0f; |
| multiplier *= m_pageInfo.m_accessibilityFontScaleFactor * |
| m_pageInfo.m_deviceScaleAdjustment; |
| return std::max(multiplier, 1.0f); |
| } |
| |
| const LayoutBlock* TextAutosizer::deepestBlockContainingAllText( |
| Cluster* cluster) { |
| if (!cluster->m_deepestBlockContainingAllText) |
| cluster->m_deepestBlockContainingAllText = |
| deepestBlockContainingAllText(cluster->m_root); |
| |
| return cluster->m_deepestBlockContainingAllText; |
| } |
| |
| // FIXME: Refactor this to look more like TextAutosizer::deepestCommonAncestor. |
| const LayoutBlock* TextAutosizer::deepestBlockContainingAllText( |
| const LayoutBlock* root) const { |
| size_t firstDepth = 0; |
| const LayoutObject* firstTextLeaf = findTextLeaf(root, firstDepth, First); |
| if (!firstTextLeaf) |
| return root; |
| |
| size_t lastDepth = 0; |
| const LayoutObject* lastTextLeaf = findTextLeaf(root, lastDepth, Last); |
| ASSERT(lastTextLeaf); |
| |
| // Equalize the depths if necessary. Only one of the while loops below will |
| // get executed. |
| const LayoutObject* firstNode = firstTextLeaf; |
| const LayoutObject* 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->isLayoutBlock()) |
| return toLayoutBlock(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 node's lowest common |
| // ancestor as isAutosizingCluster would have made them into their own |
| // independent cluster. |
| const LayoutBlock* containingBlock = firstNode->containingBlock(); |
| if (!containingBlock) |
| return root; |
| |
| ASSERT(containingBlock->isDescendantOf(root)); |
| return containingBlock; |
| } |
| |
| const LayoutObject* TextAutosizer::findTextLeaf( |
| const LayoutObject* parent, |
| size_t& depth, |
| TextLeafSearch firstOrLast) const { |
| // List items are treated as text due to the marker. |
| if (parent->isListItem()) |
| return parent; |
| |
| if (parent->isText()) |
| return parent; |
| |
| ++depth; |
| const LayoutObject* child = (firstOrLast == First) ? parent->slowFirstChild() |
| : parent->slowLastChild(); |
| while (child) { |
| // Note: At this point clusters may not have been created for these blocks |
| // so we cannot rely on m_clusters. Instead, we use a best-guess about |
| // whether the block will become a cluster. |
| if (!classifyBlock(child, INDEPENDENT)) { |
| if (const LayoutObject* leaf = findTextLeaf(child, depth, firstOrLast)) |
| return leaf; |
| } |
| child = (firstOrLast == First) ? child->nextSibling() |
| : child->previousSibling(); |
| } |
| --depth; |
| |
| return nullptr; |
| } |
| |
| void TextAutosizer::applyMultiplier(LayoutObject* layoutObject, |
| float multiplier, |
| SubtreeLayoutScope* layouter, |
| RelayoutBehavior relayoutBehavior) { |
| ASSERT(layoutObject); |
| ComputedStyle& currentStyle = layoutObject->mutableStyleRef(); |
| if (!currentStyle.getTextSizeAdjust().isAuto()) { |
| // The accessibility font scale factor is applied by the autosizer so we |
| // need to apply that scale factor on top of the text-size-adjust |
| // multiplier. |
| multiplier = currentStyle.getTextSizeAdjust().multiplier() * |
| m_pageInfo.m_accessibilityFontScaleFactor; |
| } else if (multiplier < 1) { |
| // Unlike text-size-adjust, the text autosizer should only inflate fonts. |
| multiplier = 1; |
| } |
| |
| if (currentStyle.textAutosizingMultiplier() == multiplier) |
| return; |
| |
| // We need to clone the layoutObject style to avoid breaking style sharing. |
| RefPtr<ComputedStyle> style = ComputedStyle::clone(currentStyle); |
| style->setTextAutosizingMultiplier(multiplier); |
| style->setUnique(); |
| |
| switch (relayoutBehavior) { |
| case AlreadyInLayout: |
| // Don't free currentStyle until the end of the layout pass. This allows |
| // other parts of the system to safely hold raw ComputedStyle* pointers |
| // during layout, e.g. BreakingContext::m_currentStyle. |
| m_stylesRetainedDuringLayout.append(¤tStyle); |
| |
| layoutObject->setStyleInternal(style.release()); |
| DCHECK(!layouter || layoutObject->isDescendantOf(&layouter->root())); |
| layoutObject->setNeedsLayoutAndFullPaintInvalidation( |
| LayoutInvalidationReason::TextAutosizing, MarkContainerChain, |
| layouter); |
| break; |
| |
| case LayoutNeeded: |
| DCHECK(!layouter); |
| layoutObject->setStyle(style.release()); |
| break; |
| } |
| |
| if (multiplier != 1) |
| m_pageInfo.m_hasAutosized = true; |
| |
| layoutObject->clearBaseComputedStyle(); |
| } |
| |
| bool TextAutosizer::isWiderOrNarrowerDescendant(Cluster* cluster) { |
| // FIXME: Why do we return true when hasExplicitWidth returns false?? |
| if (!cluster->m_parent || !hasExplicitWidth(cluster->m_root)) |
| return true; |
| |
| const LayoutBlock* parentDeepestBlockContainingAllText = |
| deepestBlockContainingAllText(cluster->m_parent); |
| ASSERT(m_blocksThatHaveBegunLayout.contains(cluster->m_root)); |
| ASSERT(m_blocksThatHaveBegunLayout.contains( |
| parentDeepestBlockContainingAllText)); |
| |
| float contentWidth = cluster->m_root->contentLogicalWidth().toFloat(); |
| float clusterTextWidth = |
| parentDeepestBlockContainingAllText->contentLogicalWidth().toFloat(); |
| |
| // Clusters with a root that is wider than the deepestBlockContainingAllText |
| // of their parent autosize independently of their parent. |
| if (contentWidth > clusterTextWidth) |
| return true; |
| |
| // Clusters with a root that is significantly narrower than the |
| // deepestBlockContainingAllText of their parent autosize independently of |
| // their parent. |
| static float narrowWidthDifference = 200; |
| if (clusterTextWidth - contentWidth > narrowWidthDifference) |
| return true; |
| |
| return false; |
| } |
| |
| TextAutosizer::Cluster* TextAutosizer::currentCluster() const { |
| ASSERT_WITH_SECURITY_IMPLICATION(!m_clusterStack.isEmpty()); |
| return m_clusterStack.last().get(); |
| } |
| |
| TextAutosizer::Cluster::Cluster(const LayoutBlock* root, |
| BlockFlags flags, |
| Cluster* parent, |
| Supercluster* supercluster) |
| : m_root(root), |
| m_flags(flags), |
| m_deepestBlockContainingAllText(nullptr), |
| m_parent(parent), |
| m_multiplier(0), |
| m_hasEnoughTextToAutosize(UnknownAmountOfText), |
| m_supercluster(supercluster), |
| m_hasTableAncestor(root->isTableCell() || |
| (m_parent && m_parent->m_hasTableAncestor)) {} |
| |
| #if ENABLE(ASSERT) |
| void TextAutosizer::FingerprintMapper::assertMapsAreConsistent() { |
| // For each fingerprint -> block mapping in m_blocksForFingerprint we should |
| // have an associated map from block -> fingerprint in m_fingerprints. |
| ReverseFingerprintMap::iterator end = m_blocksForFingerprint.end(); |
| for (ReverseFingerprintMap::iterator fingerprintIt = |
| m_blocksForFingerprint.begin(); |
| fingerprintIt != end; ++fingerprintIt) { |
| Fingerprint fingerprint = fingerprintIt->key; |
| BlockSet* blocks = fingerprintIt->value.get(); |
| for (BlockSet::iterator blockIt = blocks->begin(); blockIt != blocks->end(); |
| ++blockIt) { |
| const LayoutBlock* block = (*blockIt); |
| ASSERT(m_fingerprints.get(block) == fingerprint); |
| } |
| } |
| } |
| #endif |
| |
| void TextAutosizer::FingerprintMapper::add(const LayoutObject* layoutObject, |
| Fingerprint fingerprint) { |
| remove(layoutObject); |
| |
| m_fingerprints.set(layoutObject, fingerprint); |
| #if ENABLE(ASSERT) |
| assertMapsAreConsistent(); |
| #endif |
| } |
| |
| void TextAutosizer::FingerprintMapper::addTentativeClusterRoot( |
| const LayoutBlock* block, |
| Fingerprint fingerprint) { |
| add(block, fingerprint); |
| |
| ReverseFingerprintMap::AddResult addResult = |
| m_blocksForFingerprint.add(fingerprint, std::unique_ptr<BlockSet>()); |
| if (addResult.isNewEntry) |
| addResult.storedValue->value = wrapUnique(new BlockSet); |
| addResult.storedValue->value->add(block); |
| #if ENABLE(ASSERT) |
| assertMapsAreConsistent(); |
| #endif |
| } |
| |
| bool TextAutosizer::FingerprintMapper::remove( |
| const LayoutObject* layoutObject) { |
| Fingerprint fingerprint = m_fingerprints.take(layoutObject); |
| if (!fingerprint || !layoutObject->isLayoutBlock()) |
| return false; |
| |
| ReverseFingerprintMap::iterator blocksIter = |
| m_blocksForFingerprint.find(fingerprint); |
| if (blocksIter == m_blocksForFingerprint.end()) |
| return false; |
| |
| BlockSet& blocks = *blocksIter->value; |
| blocks.remove(toLayoutBlock(layoutObject)); |
| if (blocks.isEmpty()) |
| m_blocksForFingerprint.remove(blocksIter); |
| #if ENABLE(ASSERT) |
| assertMapsAreConsistent(); |
| #endif |
| return true; |
| } |
| |
| TextAutosizer::Fingerprint TextAutosizer::FingerprintMapper::get( |
| const LayoutObject* layoutObject) { |
| return m_fingerprints.get(layoutObject); |
| } |
| |
| TextAutosizer::BlockSet* |
| TextAutosizer::FingerprintMapper::getTentativeClusterRoots( |
| Fingerprint fingerprint) { |
| return m_blocksForFingerprint.get(fingerprint); |
| } |
| |
| TextAutosizer::LayoutScope::LayoutScope(LayoutBlock* block, |
| SubtreeLayoutScope* layouter) |
| : m_textAutosizer(block->document().textAutosizer()), m_block(block) { |
| if (!m_textAutosizer) |
| return; |
| |
| if (m_textAutosizer->shouldHandleLayout()) |
| m_textAutosizer->beginLayout(m_block, layouter); |
| else |
| m_textAutosizer = nullptr; |
| } |
| |
| TextAutosizer::LayoutScope::~LayoutScope() { |
| if (m_textAutosizer) |
| m_textAutosizer->endLayout(m_block); |
| } |
| |
| TextAutosizer::TableLayoutScope::TableLayoutScope(LayoutTable* table) |
| : LayoutScope(table) { |
| if (m_textAutosizer) { |
| ASSERT(m_textAutosizer->shouldHandleLayout()); |
| m_textAutosizer->inflateAutoTable(table); |
| } |
| } |
| |
| TextAutosizer::DeferUpdatePageInfo::DeferUpdatePageInfo(Page* page) |
| : m_mainFrame(page->deprecatedLocalMainFrame()) { |
| if (TextAutosizer* textAutosizer = m_mainFrame->document()->textAutosizer()) { |
| ASSERT(!textAutosizer->m_updatePageInfoDeferred); |
| textAutosizer->m_updatePageInfoDeferred = true; |
| } |
| } |
| |
| TextAutosizer::DeferUpdatePageInfo::~DeferUpdatePageInfo() { |
| if (TextAutosizer* textAutosizer = m_mainFrame->document()->textAutosizer()) { |
| ASSERT(textAutosizer->m_updatePageInfoDeferred); |
| textAutosizer->m_updatePageInfoDeferred = false; |
| textAutosizer->updatePageInfoInAllFrames(); |
| } |
| } |
| |
| float TextAutosizer::computeAutosizedFontSize(float specifiedSize, |
| float multiplier) { |
| DCHECK_GE(multiplier, 0); |
| |
| // 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; |
| // Skip linear backoff for multipliers that shrink the size or when the font |
| // sizes are small. |
| if (multiplier <= 1 || specifiedSize <= pleasantSize) { |
| computedSize = multiplier * specifiedSize; |
| } else { |
| computedSize = multiplier * pleasantSize + |
| gradientAfterPleasantSize * (specifiedSize - pleasantSize); |
| if (computedSize < specifiedSize) |
| computedSize = specifiedSize; |
| } |
| return computedSize; |
| } |
| |
| DEFINE_TRACE(TextAutosizer) { |
| visitor->trace(m_document); |
| } |
| |
| } // namespace blink |