| /* |
| * 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 <memory> |
| #include "core/dom/Document.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/LocalFrameView.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/LayoutRubyRun.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 "platform/wtf/PtrUtil.h" |
| |
| namespace blink { |
| |
| static LayoutObject* ParentElementLayoutObject( |
| const LayoutObject* layout_object) { |
| // At style recalc, the layoutObject's parent may not be attached, |
| // so we need to obtain this from the DOM tree. |
| const Node* node = layout_object->GetNode(); |
| if (!node) |
| return nullptr; |
| |
| // FIXME: This should be using LayoutTreeBuilderTraversal::parent(). |
| if (Element* parent = node->parentElement()) |
| return parent->GetLayoutObject(); |
| return nullptr; |
| } |
| |
| static bool IsNonTextAreaFormControl(const LayoutObject* layout_object) { |
| const Node* node = layout_object ? layout_object->GetNode() : nullptr; |
| if (!node || !node->IsElementNode()) |
| return false; |
| const Element* element = ToElement(node); |
| |
| return (element->IsFormControlElement() && !isHTMLTextAreaElement(element)); |
| } |
| |
| static bool IsPotentialClusterRoot(const LayoutObject* layout_object) { |
| // "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 = layout_object->GeneratingNode(); |
| if (node && !node->hasChildren() && !layout_object->IsLayoutView()) |
| return false; |
| if (!layout_object->IsLayoutBlock()) |
| return false; |
| if (layout_object->IsInline() && |
| !layout_object->Style()->IsDisplayReplacedType()) |
| return false; |
| if (layout_object->IsListItem()) |
| return (layout_object->IsFloating() || |
| layout_object->IsOutOfFlowPositioned()); |
| |
| return true; |
| } |
| |
| static bool IsIndependentDescendant(const LayoutBlock* layout_object) { |
| DCHECK(IsPotentialClusterRoot(layout_object)); |
| |
| LayoutBlock* containing_block = layout_object->ContainingBlock(); |
| return layout_object->IsLayoutView() || layout_object->IsFloating() || |
| layout_object->IsOutOfFlowPositioned() || |
| layout_object->IsTableCell() || layout_object->IsTableCaption() || |
| layout_object->IsFlexibleBoxIncludingDeprecated() || |
| (containing_block && containing_block->IsHorizontalWritingMode() != |
| layout_object->IsHorizontalWritingMode()) || |
| layout_object->Style()->IsDisplayReplacedType() || |
| layout_object->IsTextArea() || |
| layout_object->Style()->UserModify() != EUserModify::kReadOnly; |
| } |
| |
| 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 link_count = 0; |
| LayoutObject* layout_object = block->FirstChild(); |
| float matching_font_size = -1; |
| |
| while (layout_object) { |
| if (!IsPotentialClusterRoot(layout_object)) { |
| if (layout_object->IsText() && |
| ToLayoutText(layout_object)->GetText().StripWhiteSpace().length() > 3) |
| return false; |
| if (!layout_object->IsInline() || layout_object->IsBR()) |
| return false; |
| } |
| if (layout_object->Style()->IsLink()) { |
| link_count++; |
| if (matching_font_size < 0) |
| matching_font_size = layout_object->Style()->SpecifiedFontSize(); |
| else if (matching_font_size != |
| layout_object->Style()->SpecifiedFontSize()) |
| return false; |
| |
| // Skip traversing descendants of the link. |
| layout_object = layout_object->NextInPreOrderAfterChildren(block); |
| continue; |
| } |
| layout_object = layout_object->NextInPreOrder(block); |
| } |
| |
| return (link_count >= 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() >= EOverflow::kScroll) |
| 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* layout_object = block->FirstChild(); |
| while (layout_object) { |
| if (IsNonTextAreaFormControl(layout_object)) |
| return true; |
| layout_object = layout_object->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(); |
| } |
| |
| static LayoutObject* GetParent(const LayoutObject* object) { |
| LayoutObject* parent = nullptr; |
| // LayoutObject haven't added to layout tree yet |
| if (object->GetNode() && object->GetNode()->parentNode()) |
| parent = object->GetNode()->parentNode()->GetLayoutObject(); |
| return parent; |
| } |
| |
| TextAutosizer::TextAutosizer(const Document* document) |
| : document_(document), |
| first_block_to_begin_layout_(nullptr), |
| #if DCHECK_IS_ON() |
| blocks_that_have_begun_layout_(), |
| #endif |
| cluster_stack_(), |
| fingerprint_mapper_(), |
| page_info_(), |
| update_page_info_deferred_(false) { |
| } |
| |
| TextAutosizer::~TextAutosizer() {} |
| |
| void TextAutosizer::Record(LayoutBlock* block) { |
| if (!page_info_.setting_enabled_) |
| return; |
| |
| #if DCHECK_IS_ON() |
| DCHECK(!blocks_that_have_begun_layout_.Contains(block)); |
| #endif |
| if (!ClassifyBlock(block, INDEPENDENT | EXPLICIT_WIDTH)) { |
| // !everHadLayout() means the object hasn't layout yet |
| // which means this object is new added. |
| // We only deal with new added block here. |
| // If parent is new added, no need to check its children. |
| LayoutObject* parent = GetParent(block); |
| if (!block->EverHadLayout() && parent && parent->EverHadLayout()) |
| MarkSuperclusterForConsistencyCheck(parent); |
| return; |
| } |
| |
| if (Fingerprint fingerprint = ComputeFingerprint(block)) |
| fingerprint_mapper_.AddTentativeClusterRoot(block, fingerprint); |
| |
| if (!block->EverHadLayout()) |
| MarkSuperclusterForConsistencyCheck(block); |
| } |
| |
| void TextAutosizer::Record(LayoutText* text) { |
| if (!text || !ShouldHandleLayout()) |
| return; |
| LayoutObject* parent = GetParent(text); |
| if (parent && parent->EverHadLayout()) |
| MarkSuperclusterForConsistencyCheck(parent); |
| } |
| |
| void TextAutosizer::Destroy(LayoutBlock* block) { |
| if (!page_info_.setting_enabled_ && !fingerprint_mapper_.HasFingerprints()) |
| return; |
| |
| #if DCHECK_IS_ON() |
| DCHECK(!blocks_that_have_begun_layout_.Contains(block)); |
| #endif |
| |
| if (fingerprint_mapper_.Remove(block) && first_block_to_begin_layout_) { |
| // 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. |
| first_block_to_begin_layout_ = nullptr; |
| cluster_stack_.clear(); |
| } |
| } |
| |
| TextAutosizer::BeginLayoutBehavior TextAutosizer::PrepareForLayout( |
| LayoutBlock* block) { |
| #if DCHECK_IS_ON() |
| blocks_that_have_begun_layout_.insert(block); |
| #endif |
| |
| if (!first_block_to_begin_layout_) { |
| first_block_to_begin_layout_ = block; |
| PrepareClusterStack(block->Parent()); |
| if (block->IsLayoutView()) |
| CheckSuperclusterConsistency(); |
| } else if (block == CurrentCluster()->root_) { |
| // Ignore beginLayout on the same block twice. |
| // This can happen with paginated overflow. |
| return kStopLayout; |
| } |
| |
| return kContinueLayout; |
| } |
| |
| void TextAutosizer::PrepareClusterStack(LayoutObject* layout_object) { |
| if (!layout_object) |
| return; |
| PrepareClusterStack(layout_object->Parent()); |
| |
| if (layout_object->IsLayoutBlock()) { |
| LayoutBlock* block = ToLayoutBlock(layout_object); |
| #if DCHECK_IS_ON() |
| blocks_that_have_begun_layout_.insert(block); |
| #endif |
| if (Cluster* cluster = MaybeCreateCluster(block)) |
| cluster_stack_.push_back(WTF::WrapUnique(cluster)); |
| } |
| } |
| |
| void TextAutosizer::BeginLayout(LayoutBlock* block, |
| SubtreeLayoutScope* layouter) { |
| DCHECK(ShouldHandleLayout()); |
| |
| if (PrepareForLayout(block) == kStopLayout) |
| return; |
| |
| // Skip ruby's inner blocks, because these blocks already are inflated. |
| if (block->IsRubyRun() || block->IsRubyBase() || block->IsRubyText()) |
| return; |
| |
| DCHECK(!cluster_stack_.IsEmpty() || block->IsLayoutView()); |
| |
| if (Cluster* cluster = MaybeCreateCluster(block)) |
| cluster_stack_.push_back(WTF::WrapUnique(cluster)); |
| |
| DCHECK(!cluster_stack_.IsEmpty()); |
| |
| // Cells in auto-layout tables are handled separately by inflateAutoTable. |
| bool is_auto_table_cell = |
| block->IsTableCell() && |
| !ToLayoutTableCell(block)->Table()->Style()->IsFixedTableLayout(); |
| if (!is_auto_table_cell && !cluster_stack_.IsEmpty()) |
| Inflate(block, layouter); |
| } |
| |
| void TextAutosizer::InflateAutoTable(LayoutTable* table) { |
| DCHECK(table); |
| DCHECK(!table->Style()->IsFixedTableLayout()); |
| DCHECK(table->ContainingBlock()); |
| |
| Cluster* cluster = CurrentCluster(); |
| if (cluster->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, kDescendToInnerBlocks); |
| EndLayout(cell); |
| } |
| } |
| } |
| } |
| |
| void TextAutosizer::EndLayout(LayoutBlock* block) { |
| DCHECK(ShouldHandleLayout()); |
| |
| if (block == first_block_to_begin_layout_) { |
| first_block_to_begin_layout_ = nullptr; |
| cluster_stack_.clear(); |
| styles_retained_during_layout_.clear(); |
| #if DCHECK_IS_ON() |
| blocks_that_have_begun_layout_.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 (!cluster_stack_.IsEmpty() && CurrentCluster()->root_ == block) { |
| cluster_stack_.pop_back(); |
| } |
| } |
| |
| float TextAutosizer::Inflate(LayoutObject* parent, |
| SubtreeLayoutScope* layouter, |
| InflateBehavior behavior, |
| float multiplier) { |
| Cluster* cluster = CurrentCluster(); |
| bool has_text_child = false; |
| |
| LayoutObject* child = nullptr; |
| if (parent->IsRuby()) { |
| // Skip layoutRubyRun which is inline-block. |
| // Inflate rubyRun's inner blocks. |
| LayoutObject* run = parent->SlowFirstChild(); |
| if (run && run->IsRubyRun()) { |
| child = ToLayoutRubyRun(run)->FirstChild(); |
| behavior = kDescendToInnerBlocks; |
| } |
| } else if (parent->IsLayoutBlock() && |
| (parent->ChildrenInline() || behavior == kDescendToInnerBlocks)) { |
| child = ToLayoutBlock(parent)->FirstChild(); |
| } else if (parent->IsLayoutInline()) { |
| child = ToLayoutInline(parent)->FirstChild(); |
| } |
| |
| while (child) { |
| if (child->IsText()) { |
| has_text_child = true; |
| // We only calculate this multiplier on-demand to ensure the parent block |
| // of this text has entered layout. |
| if (!multiplier) |
| multiplier = |
| cluster->flags_ & SUPPRESSING ? 1.0f : ClusterMultiplier(cluster); |
| ApplyMultiplier(child, multiplier, layouter); |
| |
| if (behavior == kDescendToInnerBlocks) { |
| // The ancestor nodes might be inline-blocks. We should |
| // setPreferredLogicalWidthsDirty for ancestor nodes here. |
| child->SetPreferredLogicalWidthsDirty(); |
| } else if (parent->IsLayoutInline()) { |
| // FIXME: Investigate why MarkOnlyThis is sufficient. |
| child->SetPreferredLogicalWidthsDirty(kMarkOnlyThis); |
| } |
| } else if (child->IsLayoutInline()) { |
| multiplier = Inflate(child, layouter, behavior, multiplier); |
| } else if (child->IsLayoutBlock() && behavior == kDescendToInnerBlocks && |
| !ClassifyBlock(child, |
| INDEPENDENT | EXPLICIT_WIDTH | SUPPRESSING)) { |
| multiplier = Inflate(child, layouter, behavior, multiplier); |
| } |
| child = child->NextSibling(); |
| } |
| |
| if (has_text_child) { |
| 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(kMarkOnlyThis); |
| } |
| } |
| |
| if (page_info_.has_autosized_) |
| UseCounter::Count(*document_, WebFeature::kTextAutosizing); |
| |
| return multiplier; |
| } |
| |
| bool TextAutosizer::ShouldHandleLayout() const { |
| return page_info_.setting_enabled_ && page_info_.page_needs_autosizing_ && |
| !update_page_info_deferred_; |
| } |
| |
| bool TextAutosizer::PageNeedsAutosizing() const { |
| return page_info_.page_needs_autosizing_; |
| } |
| |
| void TextAutosizer::MarkSuperclusterForConsistencyCheck(LayoutObject* object) { |
| if (!object || !ShouldHandleLayout()) |
| return; |
| |
| Supercluster* last_supercluster = nullptr; |
| LayoutBlock* block = nullptr; |
| while (object) { |
| if (object->IsLayoutBlock()) { |
| block = ToLayoutBlock(object); |
| if (block->IsTableCell() || |
| ClassifyBlock(block, INDEPENDENT | EXPLICIT_WIDTH)) { |
| // If supercluster hasn't been created yet, create one. |
| bool is_new_entry = false; |
| Supercluster* supercluster = |
| fingerprint_mapper_.CreateSuperclusterIfNeeded(block, is_new_entry); |
| if (supercluster && supercluster->inherit_parent_multiplier_ == |
| kDontInheritMultiplier) { |
| if (supercluster->has_enough_text_to_autosize_ == kNotEnoughText) { |
| fingerprint_mapper_.GetPotentiallyInconsistentSuperclusters() |
| .insert(supercluster); |
| } |
| return; |
| } |
| if (supercluster && |
| (is_new_entry || |
| supercluster->has_enough_text_to_autosize_ == kNotEnoughText)) |
| last_supercluster = supercluster; |
| } |
| } |
| object = GetParent(object); |
| } |
| |
| // If we didn't add any supercluster, we should add one. |
| if (last_supercluster) { |
| fingerprint_mapper_.GetPotentiallyInconsistentSuperclusters().insert( |
| last_supercluster); |
| } |
| } |
| |
| void TextAutosizer::UpdatePageInfoInAllFrames() { |
| DCHECK(!document_->GetFrame() || document_->GetFrame()->IsMainFrame()); |
| |
| for (Frame* frame = document_->GetFrame(); frame; |
| frame = frame->Tree().TraverseNext()) { |
| if (!frame->IsLocalFrame()) |
| continue; |
| |
| Document* document = ToLocalFrame(frame)->GetDocument(); |
| // If document is being detached, skip updatePageInfo. |
| if (!document || !document->IsActive()) |
| continue; |
| if (TextAutosizer* text_autosizer = document->GetTextAutosizer()) |
| text_autosizer->UpdatePageInfo(); |
| } |
| } |
| |
| void TextAutosizer::UpdatePageInfo() { |
| if (update_page_info_deferred_ || !document_->GetPage() || |
| !document_->GetSettings()) |
| return; |
| |
| PageInfo previous_page_info(page_info_); |
| page_info_.setting_enabled_ = |
| document_->GetSettings()->TextAutosizingEnabled(); |
| |
| if (!page_info_.setting_enabled_ || document_->Printing()) { |
| page_info_.page_needs_autosizing_ = false; |
| } else { |
| LayoutViewItem layout_view_item = document_->GetLayoutViewItem(); |
| bool horizontal_writing_mode = |
| IsHorizontalWritingMode(layout_view_item.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 = document_->GetFrame()->Tree().Top(); |
| if (frame.IsRemoteFrame()) |
| return; |
| |
| LocalFrame& main_frame = ToLocalFrame(frame); |
| IntSize frame_size = |
| document_->GetSettings()->TextAutosizingWindowSizeOverride(); |
| if (frame_size.IsEmpty()) |
| frame_size = WindowSize(); |
| |
| page_info_.frame_width_ = |
| horizontal_writing_mode ? frame_size.Width() : frame_size.Height(); |
| |
| IntSize layout_size = main_frame.View()->GetLayoutSize(); |
| page_info_.layout_width_ = |
| horizontal_writing_mode ? layout_size.Width() : layout_size.Height(); |
| |
| // TODO(pdr): Accessibility should be moved out of the text autosizer. See: |
| // crbug.com/645717. |
| page_info_.accessibility_font_scale_factor_ = |
| document_->GetSettings()->GetAccessibilityFontScaleFactor(); |
| |
| // If the page has a meta viewport or @viewport, don't apply the device |
| // scale adjustment. |
| if (!main_frame.GetDocument() |
| ->GetViewportDescription() |
| .IsSpecifiedByAuthor()) { |
| page_info_.device_scale_adjustment_ = |
| document_->GetSettings()->GetDeviceScaleAdjustment(); |
| } else { |
| page_info_.device_scale_adjustment_ = 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. |
| page_info_.page_needs_autosizing_ = |
| !!page_info_.frame_width_ && |
| (page_info_.accessibility_font_scale_factor_ * |
| page_info_.device_scale_adjustment_ * |
| (static_cast<float>(page_info_.layout_width_) / |
| page_info_.frame_width_) > |
| 1.0f); |
| } |
| |
| if (page_info_.page_needs_autosizing_) { |
| // If page info has changed, multipliers may have changed. Force a layout to |
| // recompute them. |
| if (page_info_.frame_width_ != previous_page_info.frame_width_ || |
| page_info_.layout_width_ != previous_page_info.layout_width_ || |
| page_info_.accessibility_font_scale_factor_ != |
| previous_page_info.accessibility_font_scale_factor_ || |
| page_info_.device_scale_adjustment_ != |
| previous_page_info.device_scale_adjustment_ || |
| page_info_.setting_enabled_ != previous_page_info.setting_enabled_) |
| SetAllTextNeedsLayout(); |
| } else if (previous_page_info.has_autosized_) { |
| // 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(); |
| page_info_.has_autosized_ = false; |
| } |
| } |
| |
| IntSize TextAutosizer::WindowSize() const { |
| Page* page = document_->GetPage(); |
| DCHECK(page); |
| return page->GetVisualViewport().Size(); |
| } |
| |
| void TextAutosizer::ResetMultipliers() { |
| LayoutObject* layout_object = |
| LayoutAPIShim::LayoutObjectFrom(document_->GetLayoutViewItem()); |
| while (layout_object) { |
| if (const ComputedStyle* style = layout_object->Style()) { |
| if (style->TextAutosizingMultiplier() != 1) |
| ApplyMultiplier(layout_object, 1, nullptr, kLayoutNeeded); |
| } |
| layout_object = layout_object->NextInPreOrder(); |
| } |
| } |
| |
| void TextAutosizer::SetAllTextNeedsLayout(LayoutBlock* container) { |
| if (!container) |
| container = document_->GetLayoutView(); |
| LayoutObject* object = container; |
| while (object) { |
| if (!object->EverHadLayout()) { |
| // Object is new added node, so no need to deal with its children |
| object = object->NextInPreOrderAfterChildren(container); |
| } else { |
| if (object->IsText()) { |
| object->SetNeedsLayoutAndFullPaintInvalidation( |
| LayoutInvalidationReason::kTextAutosizing); |
| } |
| object = object->NextInPreOrder(container); |
| } |
| } |
| } |
| |
| TextAutosizer::BlockFlags TextAutosizer::ClassifyBlock( |
| const LayoutObject* layout_object, |
| BlockFlags mask) const { |
| if (!layout_object->IsLayoutBlock()) |
| return 0; |
| |
| const LayoutBlock* block = ToLayoutBlock(layout_object); |
| 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* width_provider) { |
| Cluster hypothetical_cluster(root, ClassifyBlock(root), nullptr); |
| return ClusterHasEnoughTextToAutosize(&hypothetical_cluster, width_provider); |
| } |
| |
| bool TextAutosizer::ClusterHasEnoughTextToAutosize( |
| Cluster* cluster, |
| const LayoutBlock* width_provider) { |
| if (cluster->has_enough_text_to_autosize_ != kUnknownAmountOfText) |
| return cluster->has_enough_text_to_autosize_ == kHasEnoughText; |
| |
| const LayoutBlock* root = cluster->root_; |
| if (!width_provider) |
| width_provider = 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() != |
| EUserModify::kReadOnly)) { |
| cluster->has_enough_text_to_autosize_ = kHasEnoughText; |
| return true; |
| } |
| |
| if (cluster->flags_ & SUPPRESSING) { |
| cluster->has_enough_text_to_autosize_ = kNotEnoughText; |
| return false; |
| } |
| |
| // 4 lines of text is considered enough to autosize. |
| float minimum_text_length_to_autosize = WidthFromBlock(width_provider) * 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)->GetText().StripWhiteSpace().length() * |
| descendant->Style()->SpecifiedFontSize(); |
| |
| if (length >= minimum_text_length_to_autosize) { |
| cluster->has_enough_text_to_autosize_ = kHasEnoughText; |
| return true; |
| } |
| } |
| descendant = descendant->NextInPreOrder(root); |
| } |
| |
| cluster->has_enough_text_to_autosize_ = kNotEnoughText; |
| return false; |
| } |
| |
| TextAutosizer::Fingerprint TextAutosizer::GetFingerprint( |
| LayoutObject* layout_object) { |
| Fingerprint result = fingerprint_mapper_.Get(layout_object); |
| if (!result) { |
| result = ComputeFingerprint(layout_object); |
| fingerprint_mapper_.Add(layout_object, result); |
| } |
| return result; |
| } |
| |
| TextAutosizer::Fingerprint TextAutosizer::ComputeFingerprint( |
| const LayoutObject* layout_object) { |
| Node* node = layout_object->GeneratingNode(); |
| if (!node || !node->IsElementNode()) |
| return 0; |
| |
| FingerprintSourceData data; |
| if (LayoutObject* parent = ParentElementLayoutObject(layout_object)) |
| data.parent_hash_ = GetFingerprint(parent); |
| |
| data.qualified_name_hash_ = |
| QualifiedNameHash::GetHash(ToElement(node)->TagQName()); |
| |
| if (const ComputedStyle* style = layout_object->Style()) { |
| data.packed_style_properties_ = static_cast<unsigned>(style->Direction()); |
| data.packed_style_properties_ |= |
| (static_cast<unsigned>(style->GetPosition()) << 1); |
| data.packed_style_properties_ |= |
| (static_cast<unsigned>(style->Floating()) << 4); |
| data.packed_style_properties_ |= |
| (static_cast<unsigned>(style->Display()) << 6); |
| data.packed_style_properties_ |= (style->Width().GetType() << 11); |
| // packedStyleProperties effectively using 15 bits now. |
| |
| // consider for adding: writing mode, padding. |
| |
| data.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 (layout_object->IsTableCell()) |
| data.column_ = layout_object->GetNode()->NodeIndex(); |
| |
| return StringHasher::ComputeHash<UChar>( |
| static_cast<const UChar*>(static_cast<const void*>(&data)), |
| sizeof data / sizeof(UChar)); |
| } |
| |
| TextAutosizer::Cluster* TextAutosizer::MaybeCreateCluster(LayoutBlock* block) { |
| BlockFlags flags = ClassifyBlock(block); |
| if (!(flags & POTENTIAL_ROOT)) |
| return nullptr; |
| |
| Cluster* parent_cluster = |
| cluster_stack_.IsEmpty() ? nullptr : CurrentCluster(); |
| DCHECK(parent_cluster || block->IsLayoutView()); |
| |
| // If a non-independent block would not alter the SUPPRESSING flag, it doesn't |
| // need to be a cluster. |
| bool parent_suppresses = |
| parent_cluster && (parent_cluster->flags_ & SUPPRESSING); |
| if (!(flags & INDEPENDENT) && !(flags & EXPLICIT_WIDTH) && |
| !!(flags & SUPPRESSING) == parent_suppresses) |
| return nullptr; |
| |
| bool is_new_entry = false; |
| Cluster* cluster = new Cluster( |
| block, flags, parent_cluster, |
| fingerprint_mapper_.CreateSuperclusterIfNeeded(block, is_new_entry)); |
| return cluster; |
| } |
| |
| TextAutosizer::Supercluster* |
| TextAutosizer::FingerprintMapper::CreateSuperclusterIfNeeded( |
| LayoutBlock* block, |
| bool& is_new_entry) { |
| Fingerprint fingerprint = Get(block); |
| if (!fingerprint) |
| return nullptr; |
| |
| BlockSet* roots = GetTentativeClusterRoots(fingerprint); |
| if (!roots || roots->size() < 2 || !roots->Contains(block)) |
| return nullptr; |
| |
| SuperclusterMap::AddResult add_result = |
| superclusters_.insert(fingerprint, std::unique_ptr<Supercluster>()); |
| is_new_entry = add_result.is_new_entry; |
| if (!add_result.is_new_entry) |
| return add_result.stored_value->value.get(); |
| |
| Supercluster* supercluster = new Supercluster(roots); |
| add_result.stored_value->value = WTF::WrapUnique(supercluster); |
| return supercluster; |
| } |
| |
| float TextAutosizer::ClusterMultiplier(Cluster* cluster) { |
| if (cluster->multiplier_) |
| return cluster->multiplier_; |
| |
| // FIXME: why does isWiderOrNarrowerDescendant crash on independent clusters? |
| if (!(cluster->flags_ & INDEPENDENT) && IsWiderOrNarrowerDescendant(cluster)) |
| cluster->flags_ |= WIDER_OR_NARROWER; |
| |
| if (cluster->flags_ & (INDEPENDENT | WIDER_OR_NARROWER)) { |
| if (cluster->supercluster_) { |
| cluster->multiplier_ = SuperclusterMultiplier(cluster); |
| cluster->supercluster_->inherit_parent_multiplier_ = |
| kDontInheritMultiplier; |
| } else if (ClusterHasEnoughTextToAutosize(cluster)) |
| cluster->multiplier_ = |
| MultiplierFromBlock(ClusterWidthProvider(cluster->root_)); |
| else |
| cluster->multiplier_ = 1.0f; |
| } else { |
| cluster->multiplier_ = |
| cluster->parent_ ? ClusterMultiplier(cluster->parent_) : 1.0f; |
| if (cluster->supercluster_) |
| cluster->supercluster_->inherit_parent_multiplier_ = kInheritMultiplier; |
| } |
| |
| DCHECK(cluster->multiplier_); |
| return cluster->multiplier_; |
| } |
| |
| bool TextAutosizer::SuperclusterHasEnoughTextToAutosize( |
| Supercluster* supercluster, |
| const LayoutBlock* width_provider, |
| const bool skip_layouted_nodes) { |
| if (supercluster->has_enough_text_to_autosize_ != kUnknownAmountOfText) |
| return supercluster->has_enough_text_to_autosize_ == kHasEnoughText; |
| |
| for (auto* root : *supercluster->roots_) { |
| if (skip_layouted_nodes && !root->NormalChildNeedsLayout()) |
| continue; |
| if (ClusterWouldHaveEnoughTextToAutosize(root, width_provider)) { |
| supercluster->has_enough_text_to_autosize_ = kHasEnoughText; |
| return true; |
| } |
| } |
| supercluster->has_enough_text_to_autosize_ = kNotEnoughText; |
| return false; |
| } |
| |
| float TextAutosizer::SuperclusterMultiplier(Cluster* cluster) { |
| Supercluster* supercluster = cluster->supercluster_; |
| if (!supercluster->multiplier_) { |
| const LayoutBlock* width_provider = |
| MaxClusterWidthProvider(cluster->supercluster_, cluster->root_); |
| CHECK(width_provider); |
| supercluster->multiplier_ = |
| SuperclusterHasEnoughTextToAutosize(supercluster, width_provider, false) |
| ? MultiplierFromBlock(width_provider) |
| : 1.0f; |
| } |
| DCHECK(supercluster->multiplier_); |
| return supercluster->multiplier_; |
| } |
| |
| const LayoutBlock* TextAutosizer::ClusterWidthProvider( |
| const LayoutBlock* root) const { |
| if (root->IsTable() || root->IsTableCell()) |
| return root; |
| |
| return DeepestBlockContainingAllText(root); |
| } |
| |
| const LayoutBlock* TextAutosizer::MaxClusterWidthProvider( |
| Supercluster* supercluster, |
| const LayoutBlock* current_root) const { |
| const LayoutBlock* result = nullptr; |
| if (current_root) |
| result = ClusterWidthProvider(current_root); |
| |
| float max_width = 0; |
| if (result) |
| max_width = WidthFromBlock(result); |
| |
| const BlockSet* roots = supercluster->roots_; |
| for (const auto* root : *roots) { |
| const LayoutBlock* width_provider = ClusterWidthProvider(root); |
| if (width_provider->NeedsLayout()) |
| continue; |
| float width = WidthFromBlock(width_provider); |
| if (width > max_width) { |
| max_width = width; |
| result = width_provider; |
| } |
| } |
| return result; |
| } |
| |
| float TextAutosizer::WidthFromBlock(const LayoutBlock* block) const { |
| CHECK(block); |
| CHECK(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 specified_width = |
| block->IsTableCell() |
| ? ToLayoutTableCell(block)->StyleOrColLogicalWidth() |
| : block->Style()->LogicalWidth(); |
| if (specified_width.IsFixed()) { |
| if ((width = specified_width.Value()) > 0) |
| return width; |
| } |
| if (specified_width.IsPercentOrCalc()) { |
| if (float container_width = |
| block->ContainingBlock()->ContentLogicalWidth().ToFloat()) { |
| if ((width = FloatValueForLength(specified_width, container_width)) > 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. |
| #if DCHECK_IS_ON() |
| DCHECK(blocks_that_have_begun_layout_.Contains(block) || |
| !block->NeedsLayout()); |
| #endif |
| // Block width, in CSS pixels. |
| float block_width = WidthFromBlock(block); |
| float layout_width = |
| std::min(block_width, static_cast<float>(page_info_.layout_width_)); |
| float multiplier = |
| page_info_.frame_width_ ? layout_width / page_info_.frame_width_ : 1.0f; |
| multiplier *= page_info_.accessibility_font_scale_factor_ * |
| page_info_.device_scale_adjustment_; |
| return std::max(multiplier, 1.0f); |
| } |
| |
| const LayoutBlock* TextAutosizer::DeepestBlockContainingAllText( |
| Cluster* cluster) { |
| if (!cluster->deepest_block_containing_all_text_) |
| cluster->deepest_block_containing_all_text_ = |
| DeepestBlockContainingAllText(cluster->root_); |
| |
| return cluster->deepest_block_containing_all_text_; |
| } |
| |
| // FIXME: Refactor this to look more like TextAutosizer::deepestCommonAncestor. |
| const LayoutBlock* TextAutosizer::DeepestBlockContainingAllText( |
| const LayoutBlock* root) const { |
| // To avoid font-size shaking caused by the change of LayoutView's |
| // DeepestBlockContainingAllText. |
| if (root->IsLayoutView()) |
| return root; |
| |
| size_t first_depth = 0; |
| const LayoutObject* first_text_leaf = FindTextLeaf(root, first_depth, kFirst); |
| if (!first_text_leaf) |
| return root; |
| |
| size_t last_depth = 0; |
| const LayoutObject* last_text_leaf = FindTextLeaf(root, last_depth, kLast); |
| DCHECK(last_text_leaf); |
| |
| // Equalize the depths if necessary. Only one of the while loops below will |
| // get executed. |
| const LayoutObject* first_node = first_text_leaf; |
| const LayoutObject* last_node = last_text_leaf; |
| while (first_depth > last_depth) { |
| first_node = first_node->Parent(); |
| --first_depth; |
| } |
| while (last_depth > first_depth) { |
| last_node = last_node->Parent(); |
| --last_depth; |
| } |
| |
| // Go up from both nodes until the parent is the same. Both pointers will |
| // point to the LCA then. |
| while (first_node != last_node) { |
| first_node = first_node->Parent(); |
| last_node = last_node->Parent(); |
| } |
| |
| if (first_node->IsLayoutBlock()) |
| return ToLayoutBlock(first_node); |
| |
| // 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* containing_block = first_node->ContainingBlock(); |
| if (!containing_block) |
| return root; |
| |
| DCHECK(containing_block->IsDescendantOf(root)); |
| return containing_block; |
| } |
| |
| const LayoutObject* TextAutosizer::FindTextLeaf( |
| const LayoutObject* parent, |
| size_t& depth, |
| TextLeafSearch first_or_last) 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 = (first_or_last == kFirst) |
| ? 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, first_or_last)) |
| return leaf; |
| } |
| child = (first_or_last == kFirst) ? child->NextSibling() |
| : child->PreviousSibling(); |
| } |
| --depth; |
| |
| return nullptr; |
| } |
| |
| void TextAutosizer::ApplyMultiplier(LayoutObject* layout_object, |
| float multiplier, |
| SubtreeLayoutScope* layouter, |
| RelayoutBehavior relayout_behavior) { |
| DCHECK(layout_object); |
| ComputedStyle& current_style = layout_object->MutableStyleRef(); |
| if (!current_style.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. Only apply the accessibility factor if the autosizer has |
| // determined a multiplier should be applied so that text-size-adjust:none |
| // does not cause a multiplier to be applied when it wouldn't be otherwise. |
| bool should_apply_accessibility_font_scale_factor = multiplier > 1; |
| multiplier = current_style.GetTextSizeAdjust().Multiplier(); |
| if (should_apply_accessibility_font_scale_factor) |
| multiplier *= page_info_.accessibility_font_scale_factor_; |
| } else if (multiplier < 1) { |
| // Unlike text-size-adjust, the text autosizer should only inflate fonts. |
| multiplier = 1; |
| } |
| |
| if (current_style.TextAutosizingMultiplier() == multiplier) |
| return; |
| |
| // We need to clone the layoutObject style to avoid breaking style sharing. |
| RefPtr<ComputedStyle> style = ComputedStyle::Clone(current_style); |
| style->SetTextAutosizingMultiplier(multiplier); |
| style->SetUnique(); |
| |
| switch (relayout_behavior) { |
| case kAlreadyInLayout: |
| // 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. |
| styles_retained_during_layout_.push_back(¤t_style); |
| |
| layout_object->SetStyleInternal(std::move(style)); |
| if (layout_object->IsText()) |
| ToLayoutText(layout_object)->AutosizingMultiplerChanged(); |
| DCHECK(!layouter || layout_object->IsDescendantOf(&layouter->Root())); |
| layout_object->SetNeedsLayoutAndFullPaintInvalidation( |
| LayoutInvalidationReason::kTextAutosizing, kMarkContainerChain, |
| layouter); |
| break; |
| |
| case kLayoutNeeded: |
| DCHECK(!layouter); |
| layout_object->SetStyle(std::move(style)); |
| break; |
| } |
| |
| if (multiplier != 1) |
| page_info_.has_autosized_ = true; |
| |
| layout_object->ClearBaseComputedStyle(); |
| } |
| |
| bool TextAutosizer::IsWiderOrNarrowerDescendant(Cluster* cluster) { |
| // FIXME: Why do we return true when hasExplicitWidth returns false?? |
| if (!cluster->parent_ || !HasExplicitWidth(cluster->root_)) |
| return true; |
| |
| const LayoutBlock* parent_deepest_block_containing_all_text = |
| DeepestBlockContainingAllText(cluster->parent_); |
| #if DCHECK_IS_ON() |
| DCHECK(blocks_that_have_begun_layout_.Contains(cluster->root_)); |
| DCHECK(blocks_that_have_begun_layout_.Contains( |
| parent_deepest_block_containing_all_text)); |
| #endif |
| |
| float content_width = |
| DeepestBlockContainingAllText(cluster)->ContentLogicalWidth().ToFloat(); |
| float cluster_text_width = |
| parent_deepest_block_containing_all_text->ContentLogicalWidth().ToFloat(); |
| |
| // Clusters with a root that is wider than the deepestBlockContainingAllText |
| // of their parent autosize independently of their parent. |
| if (content_width > cluster_text_width) |
| return true; |
| |
| // Clusters with a root that is significantly narrower than the |
| // deepestBlockContainingAllText of their parent autosize independently of |
| // their parent. |
| static float narrow_width_difference = 200; |
| if (cluster_text_width - content_width > narrow_width_difference) |
| return true; |
| |
| return false; |
| } |
| |
| TextAutosizer::Cluster* TextAutosizer::CurrentCluster() const { |
| SECURITY_DCHECK(!cluster_stack_.IsEmpty()); |
| return cluster_stack_.back().get(); |
| } |
| |
| TextAutosizer::Cluster::Cluster(const LayoutBlock* root, |
| BlockFlags flags, |
| Cluster* parent, |
| Supercluster* supercluster) |
| : root_(root), |
| flags_(flags), |
| deepest_block_containing_all_text_(nullptr), |
| parent_(parent), |
| multiplier_(0), |
| has_enough_text_to_autosize_(kUnknownAmountOfText), |
| supercluster_(supercluster), |
| has_table_ancestor_(root->IsTableCell() || |
| (parent_ && parent_->has_table_ancestor_)) {} |
| |
| #if DCHECK_IS_ON() |
| 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 = blocks_for_fingerprint_.end(); |
| for (ReverseFingerprintMap::iterator fingerprint_it = |
| blocks_for_fingerprint_.begin(); |
| fingerprint_it != end; ++fingerprint_it) { |
| Fingerprint fingerprint = fingerprint_it->key; |
| BlockSet* blocks = fingerprint_it->value.get(); |
| for (BlockSet::iterator block_it = blocks->begin(); |
| block_it != blocks->end(); ++block_it) { |
| const LayoutBlock* block = (*block_it); |
| DCHECK_EQ(fingerprints_.at(block), fingerprint); |
| } |
| } |
| } |
| #endif |
| |
| void TextAutosizer::FingerprintMapper::Add(LayoutObject* layout_object, |
| Fingerprint fingerprint) { |
| Remove(layout_object); |
| |
| fingerprints_.Set(layout_object, fingerprint); |
| #if DCHECK_IS_ON() |
| AssertMapsAreConsistent(); |
| #endif |
| } |
| |
| void TextAutosizer::FingerprintMapper::AddTentativeClusterRoot( |
| LayoutBlock* block, |
| Fingerprint fingerprint) { |
| Add(block, fingerprint); |
| |
| ReverseFingerprintMap::AddResult add_result = |
| blocks_for_fingerprint_.insert(fingerprint, std::unique_ptr<BlockSet>()); |
| if (add_result.is_new_entry) |
| add_result.stored_value->value = WTF::WrapUnique(new BlockSet); |
| add_result.stored_value->value->insert(block); |
| #if DCHECK_IS_ON() |
| AssertMapsAreConsistent(); |
| #endif |
| } |
| |
| bool TextAutosizer::FingerprintMapper::Remove(LayoutObject* layout_object) { |
| Fingerprint fingerprint = fingerprints_.Take(layout_object); |
| if (!fingerprint || !layout_object->IsLayoutBlock()) |
| return false; |
| |
| ReverseFingerprintMap::iterator blocks_iter = |
| blocks_for_fingerprint_.find(fingerprint); |
| if (blocks_iter == blocks_for_fingerprint_.end()) |
| return false; |
| |
| BlockSet& blocks = *blocks_iter->value; |
| blocks.erase(ToLayoutBlock(layout_object)); |
| if (blocks.IsEmpty()) { |
| blocks_for_fingerprint_.erase(blocks_iter); |
| |
| SuperclusterMap::iterator supercluster_iter = |
| superclusters_.find(fingerprint); |
| |
| if (supercluster_iter != superclusters_.end()) { |
| Supercluster* supercluster = supercluster_iter->value.get(); |
| potentially_inconsistent_superclusters_.erase(supercluster); |
| superclusters_.erase(supercluster_iter); |
| } |
| } |
| #if DCHECK_IS_ON() |
| AssertMapsAreConsistent(); |
| #endif |
| return true; |
| } |
| |
| TextAutosizer::Fingerprint TextAutosizer::FingerprintMapper::Get( |
| const LayoutObject* layout_object) { |
| return fingerprints_.at(layout_object); |
| } |
| |
| TextAutosizer::BlockSet* |
| TextAutosizer::FingerprintMapper::GetTentativeClusterRoots( |
| Fingerprint fingerprint) { |
| return blocks_for_fingerprint_.at(fingerprint); |
| } |
| |
| TextAutosizer::LayoutScope::LayoutScope(LayoutBlock* block, |
| SubtreeLayoutScope* layouter) |
| : text_autosizer_(block->GetDocument().GetTextAutosizer()), block_(block) { |
| if (!text_autosizer_) |
| return; |
| |
| if (text_autosizer_->ShouldHandleLayout()) |
| text_autosizer_->BeginLayout(block_, layouter); |
| else |
| text_autosizer_ = nullptr; |
| } |
| |
| TextAutosizer::LayoutScope::~LayoutScope() { |
| if (text_autosizer_) |
| text_autosizer_->EndLayout(block_); |
| } |
| |
| TextAutosizer::TableLayoutScope::TableLayoutScope(LayoutTable* table) |
| : LayoutScope(table) { |
| if (text_autosizer_) { |
| DCHECK(text_autosizer_->ShouldHandleLayout()); |
| text_autosizer_->InflateAutoTable(table); |
| } |
| } |
| |
| TextAutosizer::DeferUpdatePageInfo::DeferUpdatePageInfo(Page* page) |
| : main_frame_(page->DeprecatedLocalMainFrame()) { |
| if (TextAutosizer* text_autosizer = |
| main_frame_->GetDocument()->GetTextAutosizer()) { |
| DCHECK(!text_autosizer->update_page_info_deferred_); |
| text_autosizer->update_page_info_deferred_ = true; |
| } |
| } |
| |
| TextAutosizer::DeferUpdatePageInfo::~DeferUpdatePageInfo() { |
| if (TextAutosizer* text_autosizer = |
| main_frame_->GetDocument()->GetTextAutosizer()) { |
| DCHECK(text_autosizer->update_page_info_deferred_); |
| text_autosizer->update_page_info_deferred_ = false; |
| text_autosizer->UpdatePageInfoInAllFrames(); |
| } |
| } |
| |
| float TextAutosizer::ComputeAutosizedFontSize(float specified_size, |
| float multiplier) { |
| DCHECK_GE(multiplier, 0); |
| |
| // Somewhat arbitrary "pleasant" font size. |
| const float kPleasantSize = 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 kGradientAfterPleasantSize = 0.5; |
| |
| float computed_size; |
| // Skip linear backoff for multipliers that shrink the size or when the font |
| // sizes are small. |
| if (multiplier <= 1 || specified_size <= kPleasantSize) { |
| computed_size = multiplier * specified_size; |
| } else { |
| computed_size = |
| multiplier * kPleasantSize + |
| kGradientAfterPleasantSize * (specified_size - kPleasantSize); |
| if (computed_size < specified_size) |
| computed_size = specified_size; |
| } |
| return computed_size; |
| } |
| |
| void TextAutosizer::CheckSuperclusterConsistency() { |
| HashSet<Supercluster*>& potentially_inconsistent_superclusters = |
| fingerprint_mapper_.GetPotentiallyInconsistentSuperclusters(); |
| if (potentially_inconsistent_superclusters.IsEmpty()) |
| return; |
| |
| for (Supercluster* supercluster : potentially_inconsistent_superclusters) { |
| if (kHasEnoughText == supercluster->has_enough_text_to_autosize_) |
| continue; |
| |
| float old_multipiler = supercluster->multiplier_; |
| supercluster->multiplier_ = 0; |
| supercluster->has_enough_text_to_autosize_ = kUnknownAmountOfText; |
| const LayoutBlock* width_provider = |
| MaxClusterWidthProvider(supercluster, nullptr); |
| if (!width_provider) |
| continue; |
| |
| if (SuperclusterHasEnoughTextToAutosize(supercluster, width_provider, |
| true) == kHasEnoughText) { |
| for (auto* root : *supercluster->roots_) { |
| if (!root->EverHadLayout()) |
| continue; |
| |
| DCHECK(root); |
| SetAllTextNeedsLayout(root); |
| } |
| } else { |
| supercluster->multiplier_ = old_multipiler; |
| } |
| } |
| potentially_inconsistent_superclusters.clear(); |
| } |
| |
| DEFINE_TRACE(TextAutosizer) { |
| visitor->Trace(document_); |
| } |
| |
| } // namespace blink |