void RenderFlowThread::computeOverflowStateForRegions(LayoutUnit oldClientAfterEdge) { LayoutUnit height = oldClientAfterEdge; LayoutUnit offsetBreakAdjustment = 0; // Simulate a region break at height. If it points inside an auto logical height region, // then it may determine the region override logical content height. addForcedRegionBreak(height, this, false, &offsetBreakAdjustment); // During the normal layout phase of the flow thread all the auto-height regions have the overrideLogicalContentHeight set to max height. // We need to clear the overrideLogicalContentHeight for all the regions that didn't receive any content, starting with firstEmptyRegion. RenderRegion* firstEmptyRegion = 0; if (view()->normalLayoutPhase()) firstEmptyRegion = regionAtBlockOffset(height + offsetBreakAdjustment); // FIXME: the visual overflow of middle region (if it is the last one to contain any content in a render flow thread) // might not be taken into account because the render flow thread height is greater that that regions height + its visual overflow // because of how computeLogicalHeight is implemented for RenderFlowThread (as a sum of all regions height). // This means that the middle region will be marked as fit (even if it has visual overflow flowing into the next region) if (hasRenderOverflow() && ( (isHorizontalWritingMode() && visualOverflowRect().maxY() > clientBoxRect().maxY()) || (!isHorizontalWritingMode() && visualOverflowRect().maxX() > clientBoxRect().maxX()))) height = isHorizontalWritingMode() ? visualOverflowRect().maxY() : visualOverflowRect().maxX(); bool inEmptyRegionsSection = false; RenderRegion* lastReg = lastRegion(); for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { RenderRegion* region = *iter; LayoutUnit flowMin = height - (isHorizontalWritingMode() ? region->flowThreadPortionRect().y() : region->flowThreadPortionRect().x()); LayoutUnit flowMax = height - (isHorizontalWritingMode() ? region->flowThreadPortionRect().maxY() : region->flowThreadPortionRect().maxX()); RenderRegion::RegionState previousState = region->regionState(); RenderRegion::RegionState state = RenderRegion::RegionFit; if (flowMin <= 0) state = RenderRegion::RegionEmpty; if (flowMax > 0 && region == lastReg) state = RenderRegion::RegionOverset; region->setRegionState(state); // determine whether the NamedFlow object should dispatch a regionLayoutUpdate event // FIXME: currently it cannot determine whether a region whose regionOverset state remained either "fit" or "overset" has actually // changed, so it just assumes that the NamedFlow should dispatch the event if (previousState != state || state == RenderRegion::RegionFit || state == RenderRegion::RegionOverset) setDispatchRegionLayoutUpdateEvent(true); if (region == firstEmptyRegion) inEmptyRegionsSection = true; // Clear the overrideLogicalContentHeight value for autoheight regions that didn't receive any content. if (inEmptyRegionsSection && region->hasAutoLogicalHeight()) region->clearOverrideLogicalContentHeight(); } // With the regions overflow state computed we can also set the overset flag for the named flow. // If there are no valid regions in the chain, overset is true. m_overset = lastReg ? lastReg->regionState() == RenderRegion::RegionOverset : true; }
LayoutUnit RenderFlowThread::contentLogicalLeftOfFirstRegion() const { if (!hasValidRegionInfo()) return 0; for (RenderRegionList::const_iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { RenderRegion* region = *iter; if (!region->isValid()) continue; return isHorizontalWritingMode() ? region->flowThreadPortionRect().x() : region->flowThreadPortionRect().y(); } ASSERT_NOT_REACHED(); return 0; }
LayoutUnit RenderFlowThread::contentLogicalLeftOfFirstRegion() const { RenderRegion* firstValidRegionInFlow = firstRegion(); if (!firstValidRegionInFlow) return 0; return isHorizontalWritingMode() ? firstValidRegionInFlow->flowThreadPortionRect().x() : firstValidRegionInFlow->flowThreadPortionRect().y(); }
RenderRegion* RenderFlowThread::regionAtBlockOffset(LayoutUnit offset, bool extendLastRegion) const { ASSERT(!m_regionsInvalidated); // If no region matches the position and extendLastRegion is true, it will return // the last valid region. It is similar to auto extending the size of the last region. RenderRegion* lastValidRegion = 0; LayoutUnit accumulatedLogicalHeight = 0; // FIXME: The regions are always in order, optimize this search. for (RenderRegionList::const_iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { RenderRegion* region = *iter; if (offset <= 0) return region; if (extendLastRegion || region->isRenderRegionSet()) lastValidRegion = region; if (region->hasOverrideHeight() && view()->normalLayoutPhase()) { accumulatedLogicalHeight += region->overrideLogicalContentHeight(); if (offset < accumulatedLogicalHeight) return region; continue; } LayoutRect regionRect = region->flowThreadPortionRect(); accumulatedLogicalHeight += isHorizontalWritingMode() ? regionRect.height() : regionRect.width(); if (offset < accumulatedLogicalHeight) return region; } return lastValidRegion; }
RenderRegion* RenderFlowThread::regionAtBlockOffset(LayoutUnit offset, bool extendLastRegion) const { ASSERT(!m_regionsInvalidated); // If no region matches the position and extendLastRegion is true, it will return // the last valid region. It is similar to auto extending the size of the last region. RenderRegion* lastValidRegion = 0; // FIXME: The regions are always in order, optimize this search. bool useHorizontalWritingMode = isHorizontalWritingMode(); for (RenderRegionList::const_iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { RenderRegion* region = *iter; if (!region->isValid()) continue; if (offset <= 0) return region; LayoutRect regionRect = region->flowThreadPortionRect(); if ((useHorizontalWritingMode && offset < regionRect.maxY()) || (!useHorizontalWritingMode && offset < regionRect.maxX())) return region; if (extendLastRegion || region->isRenderRegionSet()) lastValidRegion = region; } return lastValidRegion; }
void RenderNamedFlowThread::computeOversetStateForRegions(LayoutUnit oldClientAfterEdge) { LayoutUnit height = oldClientAfterEdge; // FIXME: the visual overflow of middle region (if it is the last one to contain any content in a render flow thread) // might not be taken into account because the render flow thread height is greater that that regions height + its visual overflow // because of how computeLogicalHeight is implemented for RenderNamedFlowThread (as a sum of all regions height). // This means that the middle region will be marked as fit (even if it has visual overflow flowing into the next region) if (hasRenderOverflow() && ( (isHorizontalWritingMode() && visualOverflowRect().maxY() > clientBoxRect().maxY()) || (!isHorizontalWritingMode() && visualOverflowRect().maxX() > clientBoxRect().maxX()))) height = isHorizontalWritingMode() ? visualOverflowRect().maxY() : visualOverflowRect().maxX(); RenderRegion* lastReg = lastRegion(); for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { RenderRegion* region = *iter; LayoutUnit flowMin = height - (isHorizontalWritingMode() ? region->flowThreadPortionRect().y() : region->flowThreadPortionRect().x()); LayoutUnit flowMax = height - (isHorizontalWritingMode() ? region->flowThreadPortionRect().maxY() : region->flowThreadPortionRect().maxX()); RegionOversetState previousState = region->regionOversetState(); RegionOversetState state = RegionFit; if (flowMin <= 0) state = RegionEmpty; if (flowMax > 0 && region == lastReg) state = RegionOverset; region->setRegionOversetState(state); // determine whether the NamedFlow object should dispatch a regionLayoutUpdate event // FIXME: currently it cannot determine whether a region whose regionOverset state remained either "fit" or "overset" has actually // changed, so it just assumes that the NamedFlow should dispatch the event if (previousState != state || state == RegionFit || state == RegionOverset) setDispatchRegionLayoutUpdateEvent(true); if (previousState != state) setDispatchRegionOversetChangeEvent(true); } // If the number of regions has changed since we last computed the overset property, schedule the regionOversetChange event. if (previousRegionCountChanged()) { setDispatchRegionOversetChangeEvent(true); updatePreviousRegionCount(); } // With the regions overflow state computed we can also set the overset flag for the named flow. // If there are no valid regions in the chain, overset is true. m_overset = lastReg ? lastReg->regionOversetState() == RegionOverset : true; }
RenderRegion* RenderFlowThread::mapFromFlowToRegion(TransformState& transformState) const { if (!hasValidRegionInfo()) return 0; LayoutRect boxRect = transformState.mappedQuad().enclosingBoundingBox(); flipForWritingMode(boxRect); // FIXME: We need to refactor RenderObject::absoluteQuads to be able to split the quads across regions, // for now we just take the center of the mapped enclosing box and map it to a region. // Note: Using the center in order to avoid rounding errors. LayoutPoint center = boxRect.center(); RenderRegion* renderRegion = regionAtBlockOffset(isHorizontalWritingMode() ? center.y() : center.x(), true); if (!renderRegion) return 0; LayoutRect flippedRegionRect(renderRegion->flowThreadPortionRect()); flipForWritingMode(flippedRegionRect); transformState.move(renderRegion->contentBoxRect().location() - flippedRegionRect.location()); return renderRegion; }
RenderRegion* RenderMultiColumnFlowThread::mapFromFlowToRegion(TransformState& transformState) const { if (!hasValidRegionInfo()) return nullptr; // Get back into our local flow thread space. LayoutRect boxRect = transformState.mappedQuad().enclosingBoundingBox(); flipForWritingMode(boxRect); // FIXME: We need to refactor RenderObject::absoluteQuads to be able to split the quads across regions, // for now we just take the center of the mapped enclosing box and map it to a column. LayoutPoint center = boxRect.center(); LayoutUnit centerOffset = isHorizontalWritingMode() ? center.y() : center.x(); RenderRegion* renderRegion = const_cast<RenderMultiColumnFlowThread*>(this)->regionAtBlockOffset(this, centerOffset, true, DisallowRegionAutoGeneration); if (!renderRegion) return nullptr; // Now that we know which multicolumn set we hit, we need to get the appropriate translation offset for the column. RenderMultiColumnSet* columnSet = toRenderMultiColumnSet(renderRegion); LayoutPoint translationOffset = columnSet->columnTranslationForOffset(centerOffset); // Now we know how we want the rect to be translated into the region. LayoutRect flippedRegionRect(renderRegion->flowThreadPortionRect()); if (isHorizontalWritingMode()) flippedRegionRect.setHeight(columnSet->computedColumnHeight()); else flippedRegionRect.setWidth(columnSet->computedColumnHeight()); flipForWritingMode(flippedRegionRect); flippedRegionRect.moveBy(-translationOffset); // There is an additional offset to apply, which is the offset of the region within the multi-column space. transformState.move(renderRegion->contentBoxRect().location() - flippedRegionRect.location()); return renderRegion; }
// Even if we require the break to occur at offsetBreakInFlowThread, because regions may have min/max-height values, // it is possible that the break will occur at a different offset than the original one required. // offsetBreakAdjustment measures the different between the requested break offset and the current break offset. bool RenderFlowThread::addForcedRegionBreak(LayoutUnit offsetBreakInFlowThread, RenderObject* breakChild, bool isBefore, LayoutUnit* offsetBreakAdjustment) { // We take breaks into account for height computation for auto logical height regions // only in the layout phase in which we lay out the flows threads unconstrained // and we use the content breaks to determine the overrideContentLogicalHeight for // auto logical height regions. if (view()->constrainedFlowThreadsLayoutPhase()) return false; // Breaks can come before or after some objects. We need to track these objects, so that if we get // multiple breaks for the same object (for example because of multiple layouts on the same object), // we need to invalidate every other region after the old one and start computing from fresh. RenderObjectToRegionMap& mapToUse = isBefore ? m_breakBeforeToRegionMap : m_breakAfterToRegionMap; RenderObjectToRegionMap::iterator iter = mapToUse.find(breakChild); if (iter != mapToUse.end()) { RenderRegionList::iterator regionIter = m_regionList.find(iter->value); ASSERT(regionIter != m_regionList.end()); ASSERT((*regionIter)->hasAutoLogicalHeight()); initializeRegionsOverrideLogicalContentHeight(*regionIter); // We need to update the regions flow thread portion rect because we are going to process // a break on these regions. updateRegionsFlowThreadPortionRect(); } // Simulate a region break at offsetBreakInFlowThread. If it points inside an auto logical height region, // then it determines the region override logical content height. RenderRegion* region = regionAtBlockOffset(offsetBreakInFlowThread); if (!region) return false; bool overrideLogicalContentHeightComputed = false; LayoutUnit currentRegionOffsetInFlowThread = isHorizontalWritingMode() ? region->flowThreadPortionRect().y() : region->flowThreadPortionRect().x(); LayoutUnit offsetBreakInCurrentRegion = offsetBreakInFlowThread - currentRegionOffsetInFlowThread; if (region->hasAutoLogicalHeight()) { // A forced break can appear only in an auto-height region that didn't have a forced break before. // This ASSERT is a good-enough heuristic to verify the above condition. ASSERT(region->maxPageLogicalHeight() == region->overrideLogicalContentHeight()); mapToUse.set(breakChild, region); overrideLogicalContentHeightComputed = true; // Compute the region height pretending that the offsetBreakInCurrentRegion is the logicalHeight for the auto-height region. LayoutUnit regionOverrideLogicalContentHeight = region->computeReplacedLogicalHeightRespectingMinMaxHeight(offsetBreakInCurrentRegion); // The new height of this region needs to be smaller than the initial value, the max height. A forced break is the only way to change the initial // height of an auto-height region besides content ending. ASSERT(regionOverrideLogicalContentHeight <= region->maxPageLogicalHeight()); region->setOverrideLogicalContentHeight(regionOverrideLogicalContentHeight); currentRegionOffsetInFlowThread += regionOverrideLogicalContentHeight; } else currentRegionOffsetInFlowThread += isHorizontalWritingMode() ? region->flowThreadPortionRect().height() : region->flowThreadPortionRect().width(); // If the break was found inside an auto-height region its size changed so we need to recompute the flow thread portion rectangles. if (overrideLogicalContentHeightComputed) updateRegionsFlowThreadPortionRect(); if (offsetBreakAdjustment) *offsetBreakAdjustment = max<LayoutUnit>(0, currentRegionOffsetInFlowThread - offsetBreakInFlowThread); return overrideLogicalContentHeightComputed; }