/* * Copyright (C) 2011 Adobe Systems Incorporated. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "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 HOLDER 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/LayoutFlowThread.h" #include "core/layout/FragmentainerIterator.h" #include "core/layout/LayoutMultiColumnSet.h" namespace blink { LayoutFlowThread::LayoutFlowThread() : LayoutBlockFlow(nullptr), column_sets_invalidated_(false), page_logical_size_changed_(false) {} LayoutFlowThread* LayoutFlowThread::LocateFlowThreadContainingBlockOf( const LayoutObject& descendant, AncestorSearchConstraint constraint) { DCHECK(descendant.IsInsideFlowThread()); LayoutObject* curr = const_cast(&descendant); while (curr) { if (curr->IsSVGChild()) return nullptr; if (curr->IsLayoutFlowThread()) return ToLayoutFlowThread(curr); LayoutObject* container = curr->Container(); // If we're inside something strictly unbreakable (due to having scrollbars // or being writing mode roots, for instance), it's also strictly // unbreakable in any outer fragmentation context. As such, what goes on // inside any fragmentation context on the inside of this is completely // opaque to ancestor fragmentation contexts. if (constraint == kIsolateUnbreakableContainers && container && container->IsBox() && ToLayoutBox(container)->GetPaginationBreakability() == kForbidBreaks) return nullptr; curr = curr->Parent(); while (curr != container) { if (curr->IsLayoutFlowThread()) { // The nearest ancestor flow thread isn't in our containing block chain. // Then we aren't really part of any flow thread, and we should stop // looking. This happens when there are out-of-flow objects or column // spanners. return nullptr; } curr = curr->Parent(); } } return nullptr; } void LayoutFlowThread::RemoveColumnSetFromThread( LayoutMultiColumnSet* column_set) { DCHECK(column_set); multi_column_set_list_.erase(column_set); InvalidateColumnSets(); // Clear the interval tree right away, instead of leaving it around with dead // objects. Not that anyone _should_ try to access the interval tree when the // column sets are marked as invalid, but this is actually possible if other // parts of the engine has bugs that cause us to not lay out everything that // was marked for layout, so that LayoutObject::assertLaidOut() (and a LOT // of other assertions) fails. multi_column_set_interval_tree_.Clear(); } void LayoutFlowThread::ValidateColumnSets() { column_sets_invalidated_ = false; // Called to get the maximum logical width for the columnSet. UpdateLogicalWidth(); GenerateColumnSetIntervalTree(); } bool LayoutFlowThread::MapToVisualRectInAncestorSpaceInternal( const LayoutBoxModelObject* ancestor, TransformState& transform_state, VisualRectFlags visual_rect_flags) const { // A flow thread should never be an invalidation container. DCHECK_NE(ancestor, this); transform_state.Flatten(); LayoutRect rect(transform_state.LastPlanarQuad().BoundingBox()); rect = FragmentsBoundingBox(rect); transform_state.SetQuad(FloatQuad(FloatRect(rect))); return LayoutBlockFlow::MapToVisualRectInAncestorSpaceInternal( ancestor, transform_state, visual_rect_flags); } void LayoutFlowThread::UpdateLayout() { page_logical_size_changed_ = column_sets_invalidated_ && EverHadLayout(); LayoutBlockFlow::UpdateLayout(); page_logical_size_changed_ = false; } void LayoutFlowThread::ComputeLogicalHeight( LayoutUnit, LayoutUnit logical_top, LogicalExtentComputedValues& computed_values) const { computed_values.position_ = logical_top; computed_values.extent_ = LayoutUnit(); for (LayoutMultiColumnSetList::const_iterator iter = multi_column_set_list_.begin(); iter != multi_column_set_list_.end(); ++iter) { LayoutMultiColumnSet* column_set = *iter; computed_values.extent_ += column_set->LogicalHeightInFlowThread(); } } void LayoutFlowThread::AbsoluteQuadsForDescendant(const LayoutBox& descendant, Vector& quads, MapCoordinatesFlags mode) { LayoutPoint offset_from_flow_thread; for (const LayoutObject* object = &descendant; object != this;) { const LayoutObject* container = object->Container(); offset_from_flow_thread += object->OffsetFromContainer(container); object = container; } LayoutRect bounding_rect_in_flow_thread(offset_from_flow_thread, descendant.FrameRect().Size()); // Set up a fragments relative to the descendant, in the flow thread // coordinate space, and convert each of them, individually, to absolute // coordinates. for (FragmentainerIterator iterator(*this, bounding_rect_in_flow_thread); !iterator.AtEnd(); iterator.Advance()) { LayoutRect fragment = bounding_rect_in_flow_thread; // We use inclusiveIntersect() because intersect() would reset the // coordinates for zero-height objects. LayoutRect clip_rect = iterator.ClipRectInFlowThread( MultiColumnFragmentainerGroup::kBlockDirectionAxis); fragment.InclusiveIntersect(clip_rect); fragment.MoveBy(-offset_from_flow_thread); quads.push_back(descendant.LocalToAbsoluteQuad(FloatRect(fragment), mode)); } } void LayoutFlowThread::AddOutlineRects( Vector& rects, const LayoutPoint& additional_offset, IncludeBlockVisualOverflowOrNot include_block_overflows) const { Vector rects_in_flowthread; LayoutBlockFlow::AddOutlineRects(rects_in_flowthread, additional_offset, include_block_overflows); // Convert the rectangles from the flow thread coordinate space to the visual // space. The approach here is very simplistic; just calculate a bounding box // in flow thread coordinates and convert it to one in visual // coordinates. While the solution can be made more sophisticated by // e.g. using FragmentainerIterator, the usefulness isn't obvious: our // multicol implementation has practically no support for overflow in the // block direction anyway. As far as the inline direction (the column // progression direction) is concerned, we'll just include the full height of // each column involved. Should be good enough. LayoutRect union_rect; for (const auto& rect : rects_in_flowthread) union_rect.Unite(rect); rects.push_back(FragmentsBoundingBox(union_rect)); } bool LayoutFlowThread::NodeAtPoint(HitTestResult& result, const HitTestLocation& location_in_container, const LayoutPoint& accumulated_offset, HitTestAction hit_test_action) { if (hit_test_action == kHitTestBlockBackground) return false; return LayoutBlockFlow::NodeAtPoint(result, location_in_container, accumulated_offset, hit_test_action); } LayoutUnit LayoutFlowThread::PageLogicalHeightForOffset(LayoutUnit offset) { DCHECK(IsPageLogicalHeightKnown()); LayoutMultiColumnSet* column_set = ColumnSetAtBlockOffset(offset, kAssociateWithLatterPage); if (!column_set) return LayoutUnit(1); return column_set->PageLogicalHeightForOffset(offset); } LayoutUnit LayoutFlowThread::PageRemainingLogicalHeightForOffset( LayoutUnit offset, PageBoundaryRule page_boundary_rule) { DCHECK(IsPageLogicalHeightKnown()); LayoutMultiColumnSet* column_set = ColumnSetAtBlockOffset(offset, page_boundary_rule); if (!column_set) return LayoutUnit(1); return column_set->PageRemainingLogicalHeightForOffset(offset, page_boundary_rule); } void LayoutFlowThread::GenerateColumnSetIntervalTree() { // FIXME: Optimize not to clear the interval all the time. This implies // manually managing the tree nodes lifecycle. multi_column_set_interval_tree_.Clear(); multi_column_set_interval_tree_.InitIfNeeded(); for (auto column_set : multi_column_set_list_) multi_column_set_interval_tree_.Add( MultiColumnSetIntervalTree::CreateInterval( column_set->LogicalTopInFlowThread(), column_set->LogicalBottomInFlowThread(), column_set)); } LayoutUnit LayoutFlowThread::NextLogicalTopForUnbreakableContent( LayoutUnit flow_thread_offset, LayoutUnit content_logical_height) const { LayoutMultiColumnSet* column_set = ColumnSetAtBlockOffset(flow_thread_offset, kAssociateWithLatterPage); if (!column_set) return flow_thread_offset; return column_set->NextLogicalTopForUnbreakableContent( flow_thread_offset, content_logical_height); } LayoutRect LayoutFlowThread::FragmentsBoundingBox( const LayoutRect& layer_bounding_box) const { DCHECK(!RuntimeEnabledFeatures::SlimmingPaintV2Enabled() || !column_sets_invalidated_); LayoutRect result; for (auto* column_set : multi_column_set_list_) result.Unite(column_set->FragmentsBoundingBox(layer_bounding_box)); return result; } void LayoutFlowThread::FlowThreadToContainingCoordinateSpace( LayoutUnit& block_position, LayoutUnit& inline_position) const { LayoutPoint position(inline_position, block_position); // First we have to make |position| physical, because that's what offsetLeft() // expects and returns. if (!IsHorizontalWritingMode()) position = position.TransposedPoint(); position = FlipForWritingMode(position); position.Move(ColumnOffset(position)); // Make |position| logical again, and read out the values. position = FlipForWritingMode(position); if (!IsHorizontalWritingMode()) position = position.TransposedPoint(); block_position = position.Y(); inline_position = position.X(); } void LayoutFlowThread::MultiColumnSetSearchAdapter::CollectIfNeeded( const MultiColumnSetInterval& interval) { if (result_) return; if (interval.Low() <= offset_ && interval.High() > offset_) result_ = interval.Data(); } } // namespace blink