void RenderRegion::repaintFlowThreadContentRectangle(const LayoutRect& repaintRect, const LayoutRect& flowThreadPortionRect, const LayoutPoint& regionLocation, const LayoutRect* flowThreadPortionClipRect) { ASSERT(isValid()); // We only have to issue a repaint in this region if the region rect intersects the repaint rect. LayoutRect clippedRect(repaintRect); if (flowThreadPortionClipRect) { LayoutRect flippedFlowThreadPortionClipRect(*flowThreadPortionClipRect); flowThread()->flipForWritingMode(flippedFlowThreadPortionClipRect); clippedRect.intersect(flippedFlowThreadPortionClipRect); } if (clippedRect.isEmpty()) return; LayoutRect flippedFlowThreadPortionRect(flowThreadPortionRect); flowThread()->flipForWritingMode(flippedFlowThreadPortionRect); // Put the region rects into physical coordinates. // Put the region rect into the region's physical coordinate space. clippedRect.setLocation(regionLocation + (clippedRect.location() - flippedFlowThreadPortionRect.location())); // Now switch to the region's writing mode coordinate space and let it repaint itself. flipForWritingMode(clippedRect); // Issue the repaint. repaintRectangle(clippedRect); }
void RenderMultiColumnBlock::ensureColumnSets() { // This function ensures we have the correct column set information before we get into layout. // For a simple multi-column layout in continuous media, only one column set child is required. // Once a column is nested inside an enclosing pagination context, the number of column sets // required becomes 2n-1, where n is the total number of nested pagination contexts. For example: // // Column layout with no enclosing pagination model = 2 * 1 - 1 = 1 column set. // Columns inside pages = 2 * 2 - 1 = 3 column sets (bottom of first page, all the subsequent pages, then the last page). // Columns inside columns inside pages = 2 * 3 - 1 = 5 column sets. // // In addition, column spans will force a column set to "split" into before/after sets around the spanning region. // // Finally, we will need to deal with columns inside regions. If regions have variable widths, then there will need // to be unique column sets created inside any region whose width is different from its surrounding regions. This is // actually pretty similar to the spanning case, in that we break up the column sets whenever the width varies. // // FIXME: For now just make one column set. This matches the old multi-column code. // Right now our goal is just feature parity with the old multi-column code so that we can switch over to the // new code as soon as possible. if (flowThread() && !firstChild()->isRenderMultiColumnSet()) { RenderMultiColumnSet* columnSet = new (renderArena()) RenderMultiColumnSet(document(), flowThread()); columnSet->setStyle(RenderStyle::createAnonymousStyleWithDisplay(style(), BLOCK)); RenderBlock::addChild(columnSet, firstChild()); flowThread()->addRegionToThread(columnSet); } }
LayoutPoint RenderRegion::flowThreadPortionLocation() const { LayoutPoint portionLocation; LayoutRect portionRect = flowThreadPortionRect(); if (flowThread()->style().isFlippedBlocksWritingMode()) { LayoutRect flippedFlowThreadPortionRect(portionRect); flowThread()->flipForWritingMode(flippedFlowThreadPortionRect); portionLocation = flippedFlowThreadPortionRect.location(); } else portionLocation = portionRect.location(); return portionLocation; }
LayoutRect RenderMultiColumnSet::flowThreadPortionOverflowRect(const LayoutRect& portionRect, unsigned index, unsigned colCount, LayoutUnit colGap) const { // This function determines the portion of the flow thread that paints for the column. Along the inline axis, columns are // unclipped at outside edges (i.e., the first and last column in the set), and they clip to half the column // gap along interior edges. // // In the block direction, we will not clip overflow out of the top of the first column, or out of the bottom of // the last column. This applies only to the true first column and last column across all column sets. // // FIXME: Eventually we will know overflow on a per-column basis, but we can't do this until we have a painting // mode that understands not to paint contents from a previous column in the overflow area of a following column. // This problem applies to regions and pages as well and is not unique to columns. bool isFirstColumn = !index; bool isLastColumn = index == colCount - 1; bool isLeftmostColumn = style()->isLeftToRightDirection() ? isFirstColumn : isLastColumn; bool isRightmostColumn = style()->isLeftToRightDirection() ? isLastColumn : isFirstColumn; LayoutRect overflowRect(portionRect); if (isHorizontalWritingMode()) { if (isLeftmostColumn) { // Shift to the logical left overflow of the flow thread to make sure it's all covered. overflowRect.shiftXEdgeTo(min(flowThread()->visualOverflowRect().x(), portionRect.x())); } else { // Expand into half of the logical left column gap. overflowRect.shiftXEdgeTo(portionRect.x() - colGap / 2); } if (isRightmostColumn) { // Shift to the logical right overflow of the flow thread to ensure content can spill out of the column. overflowRect.shiftMaxXEdgeTo(max(flowThread()->visualOverflowRect().maxX(), portionRect.maxX())); } else { // Expand into half of the logical right column gap. overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + colGap / 2); } } else { if (isLeftmostColumn) { // Shift to the logical left overflow of the flow thread to make sure it's all covered. overflowRect.shiftYEdgeTo(min(flowThread()->visualOverflowRect().y(), portionRect.y())); } else { // Expand into half of the logical left column gap. overflowRect.shiftYEdgeTo(portionRect.y() - colGap / 2); } if (isRightmostColumn) { // Shift to the logical right overflow of the flow thread to ensure content can spill out of the column. overflowRect.shiftMaxYEdgeTo(max(flowThread()->visualOverflowRect().maxY(), portionRect.maxY())); } else { // Expand into half of the logical right column gap. overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + colGap / 2); } } return overflowRectForFlowThreadPortion(overflowRect, isFirstRegion() && isFirstColumn, isLastRegion() && isLastColumn); }
LayoutRect RenderRegion::visualOverflowRectForBoxForPropagation(const RenderBoxModelObject* box) { LayoutRect rect = visualOverflowRectForBox(box); flowThread()->flipForWritingModeLocalCoordinates(rect); return rect; }
LayoutRect RenderRegion::rectFlowPortionForBox(const RenderBox* box, const LayoutRect& rect) const { RenderRegion* startRegion = 0; RenderRegion* endRegion = 0; m_flowThread->getRegionRangeForBox(box, startRegion, endRegion); LayoutRect mappedRect = m_flowThread->mapFromLocalToFlowThread(box, rect); if (flowThread()->isHorizontalWritingMode()) { if (this != startRegion) mappedRect.shiftYEdgeTo(std::max<LayoutUnit>(logicalTopForFlowThreadContent(), mappedRect.y())); if (this != endRegion) mappedRect.setHeight(std::max<LayoutUnit>(0, std::min<LayoutUnit>(logicalBottomForFlowThreadContent() - mappedRect.y(), mappedRect.height()))); } else { if (this != startRegion) mappedRect.shiftXEdgeTo(std::max<LayoutUnit>(logicalTopForFlowThreadContent(), mappedRect.x())); if (this != endRegion) mappedRect.setWidth(std::max<LayoutUnit>(0, std::min<LayoutUnit>(logicalBottomForFlowThreadContent() - mappedRect.x(), mappedRect.width()))); } bool isLastRegionWithRegionFragmentBreak = (isLastRegion() && (style().regionFragment() == BreakRegionFragment)); if (hasOverflowClip() || isLastRegionWithRegionFragmentBreak) mappedRect.intersect(flowThreadPortionRect()); return mappedRect.isEmpty() ? mappedRect : m_flowThread->mapFromFlowThreadToLocal(box, mappedRect); }
void RenderMultiColumnSet::paintColumnContents(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { // For each rectangle, set it as the region rectangle and then let flow thread painting do the rest. // We make multiple calls to paintFlowThreadPortionInRegion, changing the rectangles each time. unsigned colCount = columnCount(); if (!colCount) return; LayoutUnit colGap = columnGap(); for (unsigned i = 0; i < colCount; i++) { // First we get the column rect, which is in our local coordinate space, and we make it physical and apply // the paint offset to it. That gives us the physical location that we want to paint the column at. LayoutRect colRect = columnRectAt(i); flipForWritingMode(colRect); colRect.moveBy(paintOffset); // Next we get the portion of the flow thread that corresponds to this column. LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); // Now get the overflow rect that corresponds to the column. LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); // Do the paint with the computed rects. flowThread()->paintFlowThreadPortionInRegion(paintInfo, this, flowThreadPortion, flowThreadOverflowPortion, colRect.location()); } }
LayoutRect RenderRegion::rectFlowPortionForBox(const RenderBox* box, const LayoutRect& rect) const { RenderRegion* startRegion = 0; RenderRegion* endRegion = 0; m_flowThread->getRegionRangeForBox(box, startRegion, endRegion); LayoutRect mappedRect = m_flowThread->mapFromLocalToFlowThread(box, rect); if (flowThread()->isHorizontalWritingMode()) { if (this != startRegion) mappedRect.shiftYEdgeTo(std::max<LayoutUnit>(logicalTopForFlowThreadContent(), mappedRect.y())); if (this != endRegion) mappedRect.setHeight(std::max<LayoutUnit>(0, std::min<LayoutUnit>(logicalBottomForFlowThreadContent() - mappedRect.y(), mappedRect.height()))); } else { if (this != startRegion) mappedRect.shiftXEdgeTo(std::max<LayoutUnit>(logicalTopForFlowThreadContent(), mappedRect.x())); if (this != endRegion) mappedRect.setWidth(std::max<LayoutUnit>(0, std::min<LayoutUnit>(logicalBottomForFlowThreadContent() - mappedRect.x(), mappedRect.width()))); } if (shouldClipFlowThreadContent()) { LayoutRect portionRect; if (isRenderNamedFlowFragment()) portionRect = toRenderNamedFlowFragment(this)->flowThreadPortionRectForClipping(this == startRegion, this == endRegion); else portionRect = flowThreadPortionRect(); mappedRect.intersect(portionRect); } return mappedRect.isEmpty() ? mappedRect : m_flowThread->mapFromFlowThreadToLocal(box, mappedRect); }
void RenderMultiColumnSet::expandToEncompassFlowThreadContentsIfNeeded() { ASSERT(multiColumnFlowThread()->lastMultiColumnSet() == this); LayoutRect rect(flowThreadPortionRect()); // Get the offset within the flow thread in its block progression direction. Then get the // flow thread's remaining logical height including its overflow and expand our rect // to encompass that remaining height and overflow. The idea is that we will generate // additional columns and pages to hold that overflow, since people do write bad // content like <body style="height:0px"> in multi-column layouts. bool isHorizontal = flowThread()->isHorizontalWritingMode(); LayoutUnit logicalTopOffset = isHorizontal ? rect.y() : rect.x(); LayoutRect layoutRect = flowThread()->layoutOverflowRect(); LayoutUnit logicalHeightWithOverflow = (isHorizontal ? layoutRect.maxY() : layoutRect.maxX()) - logicalTopOffset; setFlowThreadPortionRect(LayoutRect(rect.x(), rect.y(), isHorizontal ? rect.width() : logicalHeightWithOverflow, isHorizontal ? logicalHeightWithOverflow : rect.height())); }
void RenderRegion::adjustRegionBoundsFromFlowThreadPortionRect(const LayoutPoint& layerOffset, LayoutRect& regionBounds) { LayoutRect flippedFlowThreadPortionRect = flowThreadPortionRect(); flowThread()->flipForWritingMode(flippedFlowThreadPortionRect); regionBounds.moveBy(flippedFlowThreadPortionRect.location()); UNUSED_PARAM(layerOffset); }
PositionWithAffinity LayoutMultiColumnSet::positionForPoint(const LayoutPoint& point) { // Convert the visual point to a flow thread point. const MultiColumnFragmentainerGroup& row = fragmentainerGroupAtVisualPoint(point); LayoutPoint flowThreadPoint = row.visualPointToFlowThreadPoint(point + row.offsetFromColumnSet()); // Then drill into the flow thread, where we'll find the actual content. return flowThread()->positionForPoint(flowThreadPoint); }
void RenderRegionSet::expandToEncompassFlowThreadContentsIfNeeded() { // Whenever the last region is a set, it always expands its region rect to consume all // of the flow thread content. This is because it is always capable of generating an // infinite number of boxes in order to hold all of the remaining content. LayoutRect rect(regionRect()); // Get the offset within the flow thread in its block progression direction. Then get the // flow thread's remaining logical height including its overflow and expand our rect // to encompass that remaining height and overflow. The idea is that we will generate // additional columns and pages to hold that overflow, since people do write bad // content like <body style="height:0px"> in multi-column layouts. bool isHorizontal = flowThread()->isHorizontalWritingMode(); LayoutUnit logicalTopOffset = isHorizontal ? rect.y() : rect.x(); LayoutRect layoutRect = flowThread()->layoutOverflowRect(); LayoutUnit logicalHeightWithOverflow = (isHorizontal ? layoutRect.maxY() - flowThread()->y() : layoutRect.maxX() - flowThread()->x()) - logicalTopOffset; setRegionRect(LayoutRect(rect.x(), rect.y(), isHorizontal ? rect.width() : logicalHeightWithOverflow, isHorizontal ? logicalHeightWithOverflow : rect.height())); }
unsigned RenderMultiColumnSet::columnCount() const { if (!computedColumnHeight()) return 0; // Our region rect determines our column count. We have as many columns as needed to fit all the content. LayoutUnit logicalHeightInColumns = flowThread()->isHorizontalWritingMode() ? flowThreadPortionRect().height() : flowThreadPortionRect().width(); return ceil(static_cast<float>(logicalHeightInColumns) / computedColumnHeight()); }
LayoutRect RenderNamedFlowFragment::visualOverflowRect() const { if (isValid()) { RenderBoxRegionInfo* boxInfo = renderBoxRegionInfo(flowThread()); if (boxInfo && boxInfo->overflow()) return boxInfo->overflow()->visualOverflowRect(); } return RenderRegion::visualOverflowRect(); }
unsigned RenderMultiColumnSet::columnCount() const { // We must always return a value of 1 or greater. Column count = 0 is a meaningless situation, // and will confuse and cause problems in other parts of the code. if (!computedColumnHeight()) return 1; // Our portion rect determines our column count. We have as many columns as needed to fit all the content. LayoutUnit logicalHeightInColumns = flowThread()->isHorizontalWritingMode() ? flowThreadPortionRect().height() : flowThreadPortionRect().width(); unsigned count = ceil(static_cast<float>(logicalHeightInColumns) / computedColumnHeight()); ASSERT(count >= 1); return count; }
LayoutRect RenderNamedFlowFragment::flowThreadPortionRectForClipping(bool isFirstRegionInRange, bool isLastRegionInRange) const { // Elements flowed into a region should not be painted past the region's content box // if they continue to flow into another region in that direction. // If they do not continue into another region in that direction, they should be // painted all the way to the region's border box. // Regions with overflow:hidden will apply clip at the border box, not the content box. LayoutRect clippingRect = flowThreadPortionRect(); RenderBlockFlow& container = fragmentContainer(); if (container.style().hasPadding()) { if (isFirstRegionInRange) { if (flowThread()->isHorizontalWritingMode()) { clippingRect.move(0, -container.paddingBefore()); clippingRect.expand(0, container.paddingBefore()); } else { clippingRect.move(-container.paddingBefore(), 0); clippingRect.expand(container.paddingBefore(), 0); } } if (isLastRegionInRange) { if (flowThread()->isHorizontalWritingMode()) clippingRect.expand(0, container.paddingAfter()); else clippingRect.expand(container.paddingAfter(), 0); } if (flowThread()->isHorizontalWritingMode()) { clippingRect.move(-container.paddingStart(), 0); clippingRect.expand(container.paddingStart() + container.paddingEnd(), 0); } else { clippingRect.move(0, -container.paddingStart()); clippingRect.expand(0, container.paddingStart() + container.paddingEnd()); } } return clippingRect; }
void RenderRegion::addVisualOverflowForBox(const RenderBox* box, const LayoutRect& rect) { if (rect.isEmpty()) return; RefPtr<RenderOverflow> regionOverflow; ensureOverflowForBox(box, regionOverflow, false); if (!regionOverflow) return; LayoutRect flippedRect = rect; flowThread()->flipForWritingModeLocalCoordinates(flippedRect); regionOverflow->addVisualOverflow(flippedRect); }
void LayoutMultiColumnSpannerPlaceholder::layoutObjectInFlowThreadStyleDidChange(const ComputedStyle* oldStyle) { LayoutBox* objectInFlowThread = m_layoutObjectInFlowThread; if (flowThread()->removeSpannerPlaceholderIfNoLongerValid(objectInFlowThread)) { // No longer a valid spanner, due to style changes. |this| is now dead. if (objectInFlowThread->style()->hasOutOfFlowPosition() && !oldStyle->hasOutOfFlowPosition()) { // We went from being a spanner to being out-of-flow positioned. When an object becomes // out-of-flow positioned, we need to lay out its parent, since that's where the // now-out-of-flow object gets added to the right containing block for out-of-flow // positioned objects. Since neither a spanner nor an out-of-flow object is guaranteed // to have this parent in its containing block chain, we need to mark it here, or we // risk that the object isn't laid out. objectInFlowThread->parent()->setNeedsLayout(LayoutInvalidationReason::ColumnsChanged); } return; } updateMarginProperties(); }
bool RenderMultiColumnSet::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action) { LayoutPoint adjustedLocation = accumulatedOffset + location(); // Check our bounds next. For this purpose always assume that we can only be hit in the // foreground phase (which is true for replaced elements like images). // FIXME: Once we support overflow, we need to intersect with that and not with the bounds rect. LayoutRect boundsRect = borderBoxRectInRegion(locationInContainer.region()); boundsRect.moveBy(adjustedLocation); if (!visibleToHitTesting() || action != HitTestForeground || !locationInContainer.intersects(boundsRect)) return false; // The point is in one specific column. Since columns can't overlap, we don't ever have to test // multiple columns. Put the // FIXME: It would be nice to jump right to the specific column by just doing math on the point. Since columns // can't overlap, we shouldn't have to walk every column like this. The old column code walked all the columns, though, // so this is no worse. We'd have to watch out for rect-based hit testing, though, which actually could overlap // multiple columns. LayoutUnit colGap = columnGap(); unsigned colCount = columnCount(); for (unsigned i = 0; i < colCount; i++) { // First we get the column rect, which is in our local coordinate space, and we make it physical and apply // the hit test offset to it. That gives us the physical location that we want to paint the column at. LayoutRect colRect = columnRectAt(i); flipForWritingMode(colRect); colRect.moveBy(adjustedLocation); // Next we get the portion of the flow thread that corresponds to this column. LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); // Now get the overflow rect that corresponds to the column. LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); // Do the hit test with the computed rects. if (flowThread()->hitTestFlowThreadPortionInRegion(this, flowThreadPortion, flowThreadOverflowPortion, request, result, locationInContainer, colRect.location())) return true; } updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation)); return !result.addNodeToRectBasedTestResult(node(), request, locationInContainer, boundsRect); }
void RenderRegion::setRegionObjectsRegionStyle() { if (!hasCustomRegionStyle()) return; // Start from content nodes and recursively compute the style in region for the render objects below. // If the style in region was already computed, used that style instead of computing a new one. RenderNamedFlowThread* namedFlow = view()->flowThreadController()->ensureRenderFlowThreadWithName(style()->regionThread()); const NamedFlowContentNodes& contentNodes = namedFlow->contentNodes(); for (NamedFlowContentNodes::const_iterator iter = contentNodes.begin(), end = contentNodes.end(); iter != end; ++iter) { const Node* node = *iter; // The list of content nodes contains also the nodes with display:none. if (!node->renderer()) continue; RenderObject* object = node->renderer(); // If the content node does not flow any of its children in this region, // we do not compute any style for them in this region. if (!flowThread()->objectInFlowRegion(object, this)) continue; // If the object has style in region, use that instead of computing a new one. RenderObjectRegionStyleMap::iterator it = m_renderObjectRegionStyle.find(object); RefPtr<RenderStyle> objectStyleInRegion; bool objectRegionStyleCached = false; if (it != m_renderObjectRegionStyle.end()) { objectStyleInRegion = it->value.style; ASSERT(it->value.cached); objectRegionStyleCached = true; } else objectStyleInRegion = computeStyleInRegion(object); setObjectStyleInRegion(object, objectStyleInRegion, objectRegionStyleCached); computeChildrenStyleInRegion(object); } }
void RenderNamedFlowFragment::setRegionObjectsRegionStyle() { if (!hasCustomRegionStyle()) return; // Start from content nodes and recursively compute the style in region for the render objects below. // If the style in region was already computed, used that style instead of computing a new one. const RenderNamedFlowThread& namedFlow = view().flowThreadController().ensureRenderFlowThreadWithName(style().regionThread()); const NamedFlowContentElements& contentElements = namedFlow.contentElements(); for (const auto& element : contentElements) { // The list of content nodes contains also the nodes with display:none. if (!element->renderer()) continue; RenderElement* object = element->renderer(); // If the content node does not flow any of its children in this region, // we do not compute any style for them in this region. if (!flowThread()->objectInFlowRegion(object, this)) continue; // If the object has style in region, use that instead of computing a new one. auto it = m_renderObjectRegionStyle.find(object); std::unique_ptr<RenderStyle> objectStyleInRegion; bool objectRegionStyleCached = false; if (it != m_renderObjectRegionStyle.end()) { objectStyleInRegion = RenderStyle::clonePtr(*it->value.style); ASSERT(it->value.cached); objectRegionStyleCached = true; } else objectStyleInRegion = computeStyleInRegion(*object, style()); setObjectStyleInRegion(object, WTFMove(objectStyleInRegion), objectRegionStyleCached); computeChildrenStyleInRegion(*object); } }
void RenderMultiColumnSet::repaintFlowThreadContent(const LayoutRect& repaintRect, bool immediate) const { // Figure out the start and end columns and only check within that range so that we don't walk the // entire column set. Put the repaint rect into flow thread coordinates by flipping it first. LayoutRect flowThreadRepaintRect(repaintRect); flowThread()->flipForWritingMode(flowThreadRepaintRect); // Now we can compare this rect with the flow thread portions owned by each column. First let's // just see if the repaint rect intersects our flow thread portion at all. LayoutRect clippedRect(flowThreadRepaintRect); clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect()); if (clippedRect.isEmpty()) return; // Now we know we intersect at least one column. Let's figure out the logical top and logical // bottom of the area we're repainting. LayoutUnit repaintLogicalTop = isHorizontalWritingMode() ? flowThreadRepaintRect.y() : flowThreadRepaintRect.x(); LayoutUnit repaintLogicalBottom = (isHorizontalWritingMode() ? flowThreadRepaintRect.maxY() : flowThreadRepaintRect.maxX()) - 1; unsigned startColumn = columnIndexAtOffset(repaintLogicalTop); unsigned endColumn = columnIndexAtOffset(repaintLogicalBottom); LayoutUnit colGap = columnGap(); unsigned colCount = columnCount(); for (unsigned i = startColumn; i <= endColumn; i++) { LayoutRect colRect = columnRectAt(i); // Get the portion of the flow thread that corresponds to this column. LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); // Now get the overflow rect that corresponds to the column. LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); // Do a repaint for this specific column. repaintFlowThreadContentRectangle(repaintRect, immediate, flowThreadPortion, flowThreadOverflowPortion, colRect.location()); } }
LayoutUnit RenderRegion::logicalBottomOfFlowThreadContentRect(const LayoutRect& rect) const { ASSERT(isValid()); return flowThread()->isHorizontalWritingMode() ? rect.maxY() : rect.maxX(); }
RenderNamedFlowThread* RenderNamedFlowFragment::namedFlowThread() const { return downcast<RenderNamedFlowThread>(flowThread()); }
void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) { // The two rectangles passed to this method are physical, except that we pretend that there's // only one long column (that's how a flow thread works). // // Then there's the output from this method - the stuff we put into the list of fragments. The // fragment.paginationOffset point is the actual physical translation required to get from a // location in the flow thread to a location in a given column. The fragment.paginationClip // rectangle, on the other hand, is in the same coordinate system as the two rectangles passed // to this method (flow thread coordinates). // // All other rectangles in this method are sized physically, and the inline direction coordinate // is physical too, but the block direction coordinate is "logical top". This is the same as // e.g. RenderBox::frameRect(). These rectangles also pretend that there's only one long column, // i.e. they are for the flow thread. // Put the layer bounds into flow thread-local coordinates by flipping it first. Since we're in // a renderer, most rectangles are represented this way. LayoutRect layerBoundsInFlowThread(layerBoundingBox); flowThread()->flipForWritingMode(layerBoundsInFlowThread); // Now we can compare with the flow thread portions owned by each column. First let's // see if the rect intersects our flow thread portion at all. LayoutRect clippedRect(layerBoundsInFlowThread); clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect()); if (clippedRect.isEmpty()) return; // Now we know we intersect at least one column. Let's figure out the logical top and logical // bottom of the area we're checking. LayoutUnit layerLogicalTop = isHorizontalWritingMode() ? layerBoundsInFlowThread.y() : layerBoundsInFlowThread.x(); LayoutUnit layerLogicalBottom = (isHorizontalWritingMode() ? layerBoundsInFlowThread.maxY() : layerBoundsInFlowThread.maxX()) - 1; // Figure out the start and end columns and only check within that range so that we don't walk the // entire column set. unsigned startColumn = columnIndexAtOffset(layerLogicalTop); unsigned endColumn = columnIndexAtOffset(layerLogicalBottom); LayoutUnit colLogicalWidth = computedColumnWidth(); LayoutUnit colGap = columnGap(); unsigned colCount = columnCount(); for (unsigned i = startColumn; i <= endColumn; i++) { // Get the portion of the flow thread that corresponds to this column. LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); // Now get the overflow rect that corresponds to the column. LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); // In order to create a fragment we must intersect the portion painted by this column. LayoutRect clippedRect(layerBoundsInFlowThread); clippedRect.intersect(flowThreadOverflowPortion); if (clippedRect.isEmpty()) continue; // We also need to intersect the dirty rect. We have to apply a translation and shift based off // our column index. LayoutPoint translationOffset; LayoutUnit inlineOffset = i * (colLogicalWidth + colGap); if (!style()->isLeftToRightDirection()) inlineOffset = -inlineOffset; translationOffset.setX(inlineOffset); LayoutUnit blockOffset = isHorizontalWritingMode() ? -flowThreadPortion.y() : -flowThreadPortion.x(); if (isFlippedBlocksWritingMode(style()->writingMode())) blockOffset = -blockOffset; translationOffset.setY(blockOffset); if (!isHorizontalWritingMode()) translationOffset = translationOffset.transposedPoint(); // FIXME: The translation needs to include the multicolumn set's content offset within the // multicolumn block as well. This won't be an issue until we start creating multiple multicolumn sets. // Shift the dirty rect to be in flow thread coordinates with this translation applied. LayoutRect translatedDirtyRect(dirtyRect); translatedDirtyRect.moveBy(-translationOffset); // See if we intersect the dirty rect. clippedRect = layerBoundingBox; clippedRect.intersect(translatedDirtyRect); if (clippedRect.isEmpty()) continue; // Something does need to paint in this column. Make a fragment now and supply the physical translation // offset and the clip rect for the column with that offset applied. LayerFragment fragment; fragment.paginationOffset = translationOffset; LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion); // Flip it into more a physical (RenderLayer-style) rectangle. flowThread()->flipForWritingMode(flippedFlowThreadOverflowPortion); fragment.paginationClip = flippedFlowThreadOverflowPortion; fragments.append(fragment); } }
void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) { // Let's start by introducing the different coordinate systems involved here. They are different // in how they deal with writing modes and columns. RenderLayer rectangles tend to be more // physical than the rectangles used in RenderObject & co. // // The two rectangles passed to this method are physical, except that we pretend that there's // only one long column (that's the flow thread). They are relative to the top left corner of // the flow thread. All rectangles being compared to the dirty rect also need to be in this // coordinate system. // // Then there's the output from this method - the stuff we put into the list of fragments. The // translationOffset point is the actual physical translation required to get from a location in // the flow thread to a location in some column. The paginationClip rectangle is in the same // coordinate system as the two rectangles passed to this method (i.e. physical, in flow thread // coordinates, pretending that there's only one long column). // // All other rectangles in this method are slightly less physical, when it comes to how they are // used with different writing modes, but they aren't really logical either. They are just like // RenderBox::frameRect(). More precisely, the sizes are physical, and the inline direction // coordinate is too, but the block direction coordinate is always "logical top". These // rectangles also pretend that there's only one long column, i.e. they are for the flow thread. // // To sum up: input and output from this method are "physical" RenderLayer-style rectangles and // points, while inside this method we mostly use the RenderObject-style rectangles (with the // block direction coordinate always being logical top). // Put the layer bounds into flow thread-local coordinates by flipping it first. Since we're in // a renderer, most rectangles are represented this way. LayoutRect layerBoundsInFlowThread(layerBoundingBox); flowThread()->flipForWritingMode(layerBoundsInFlowThread); // Now we can compare with the flow thread portions owned by each column. First let's // see if the rect intersects our flow thread portion at all. LayoutRect clippedRect(layerBoundsInFlowThread); clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect()); if (clippedRect.isEmpty()) return; // Now we know we intersect at least one column. Let's figure out the logical top and logical // bottom of the area we're checking. LayoutUnit layerLogicalTop = isHorizontalWritingMode() ? layerBoundsInFlowThread.y() : layerBoundsInFlowThread.x(); LayoutUnit layerLogicalBottom = (isHorizontalWritingMode() ? layerBoundsInFlowThread.maxY() : layerBoundsInFlowThread.maxX()) - 1; // Figure out the start and end columns and only check within that range so that we don't walk the // entire column set. unsigned startColumn = columnIndexAtOffset(layerLogicalTop); unsigned endColumn = columnIndexAtOffset(layerLogicalBottom); LayoutUnit colLogicalWidth = computedColumnWidth(); LayoutUnit colGap = columnGap(); unsigned colCount = columnCount(); RenderBlockFlow* parentFlow = toRenderBlockFlow(parent()); bool progressionReversed = parentFlow->multiColumnFlowThread()->progressionIsReversed(); bool progressionIsInline = parentFlow->multiColumnFlowThread()->progressionIsInline(); LayoutUnit initialBlockOffset = initialBlockOffsetForPainting(); for (unsigned i = startColumn; i <= endColumn; i++) { // Get the portion of the flow thread that corresponds to this column. LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); // Now get the overflow rect that corresponds to the column. LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); // In order to create a fragment we must intersect the portion painted by this column. LayoutRect clippedRect(layerBoundsInFlowThread); clippedRect.intersect(flowThreadOverflowPortion); if (clippedRect.isEmpty()) continue; // We also need to intersect the dirty rect. We have to apply a translation and shift based off // our column index. LayoutPoint translationOffset; LayoutUnit inlineOffset = progressionIsInline ? i * (colLogicalWidth + colGap) : LayoutUnit(); bool leftToRight = style().isLeftToRightDirection() ^ progressionReversed; if (!leftToRight) { inlineOffset = -inlineOffset; if (progressionReversed) inlineOffset += contentLogicalWidth() - colLogicalWidth; } translationOffset.setX(inlineOffset); LayoutUnit blockOffset = initialBlockOffset + (isHorizontalWritingMode() ? -flowThreadPortion.y() : -flowThreadPortion.x()); if (!progressionIsInline) { if (!progressionReversed) blockOffset = i * colGap; else blockOffset -= i * (computedColumnHeight() + colGap); } if (isFlippedBlocksWritingMode(style().writingMode())) blockOffset = -blockOffset; translationOffset.setY(blockOffset); if (!isHorizontalWritingMode()) translationOffset = translationOffset.transposedPoint(); // FIXME: The translation needs to include the multicolumn set's content offset within the // multicolumn block as well. This won't be an issue until we start creating multiple multicolumn sets. // Shift the dirty rect to be in flow thread coordinates with this translation applied. LayoutRect translatedDirtyRect(dirtyRect); translatedDirtyRect.moveBy(-translationOffset); // See if we intersect the dirty rect. clippedRect = layerBoundingBox; clippedRect.intersect(translatedDirtyRect); if (clippedRect.isEmpty()) continue; // Something does need to paint in this column. Make a fragment now and supply the physical translation // offset and the clip rect for the column with that offset applied. LayerFragment fragment; fragment.paginationOffset = translationOffset; LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion); // Flip it into more a physical (RenderLayer-style) rectangle. flowThread()->flipForWritingMode(flippedFlowThreadOverflowPortion); fragment.paginationClip = flippedFlowThreadOverflowPortion; fragments.append(fragment); } }
void RenderMultiColumnSet::collectLayerFragments(Vector<LayerFragment>& fragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) { // Put the layer bounds into flow thread-local coordinates by flipping it first. LayoutRect layerBoundsInFlowThread(layerBoundingBox); flowThread()->flipForWritingMode(layerBoundsInFlowThread); // Do the same for the dirty rect. LayoutRect dirtyRectInFlowThread(dirtyRect); flowThread()->flipForWritingMode(dirtyRectInFlowThread); // Now we can compare with the flow thread portions owned by each column. First let's // see if the rect intersects our flow thread portion at all. LayoutRect clippedRect(layerBoundsInFlowThread); clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect()); if (clippedRect.isEmpty()) return; // Now we know we intersect at least one column. Let's figure out the logical top and logical // bottom of the area we're checking. LayoutUnit layerLogicalTop = isHorizontalWritingMode() ? layerBoundsInFlowThread.y() : layerBoundsInFlowThread.x(); LayoutUnit layerLogicalBottom = (isHorizontalWritingMode() ? layerBoundsInFlowThread.maxY() : layerBoundsInFlowThread.maxX()) - 1; // Figure out the start and end columns and only check within that range so that we don't walk the // entire column set. unsigned startColumn = columnIndexAtOffset(layerLogicalTop); unsigned endColumn = columnIndexAtOffset(layerLogicalBottom); LayoutUnit colLogicalWidth = computedColumnWidth(); LayoutUnit colGap = columnGap(); unsigned colCount = columnCount(); for (unsigned i = startColumn; i <= endColumn; i++) { // Get the portion of the flow thread that corresponds to this column. LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); // Now get the overflow rect that corresponds to the column. LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); // In order to create a fragment we must intersect the portion painted by this column. LayoutRect clippedRect(layerBoundsInFlowThread); clippedRect.intersect(flowThreadOverflowPortion); if (clippedRect.isEmpty()) continue; // We also need to intersect the dirty rect. We have to apply a translation and shift based off // our column index. LayoutPoint translationOffset; LayoutUnit inlineOffset = i * (colLogicalWidth + colGap); if (!style()->isLeftToRightDirection()) inlineOffset = -inlineOffset; translationOffset.setX(inlineOffset); LayoutUnit blockOffset = isHorizontalWritingMode() ? -flowThreadPortion.y() : -flowThreadPortion.x(); if (isFlippedBlocksWritingMode(style()->writingMode())) blockOffset = -blockOffset; translationOffset.setY(blockOffset); if (!isHorizontalWritingMode()) translationOffset = translationOffset.transposedPoint(); // FIXME: The translation needs to include the multicolumn set's content offset within the // multicolumn block as well. This won't be an issue until we start creating multiple multicolumn sets. // Shift the dirty rect to be in flow thread coordinates with this translation applied. LayoutRect translatedDirtyRect(dirtyRectInFlowThread); translatedDirtyRect.moveBy(-translationOffset); // See if we intersect the dirty rect. clippedRect = layerBoundsInFlowThread; clippedRect.intersect(translatedDirtyRect); if (clippedRect.isEmpty()) continue; // Something does need to paint in this column. Make a fragment now and supply the physical translation // offset and the clip rect for the column with that offset applied. LayerFragment fragment; fragment.paginationOffset = translationOffset; LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion); flipForWritingMode(flippedFlowThreadOverflowPortion); fragment.paginationClip = flippedFlowThreadOverflowPortion; fragments.append(fragment); } }
RenderNamedFlowThread* RenderNamedFlowFragment::namedFlowThread() const { return toRenderNamedFlowThread(flowThread()); }
LayoutUnit RenderRegion::pageLogicalTopForOffset(LayoutUnit /* offset */) const { return flowThread()->isHorizontalWritingMode() ? flowThreadPortionRect().y() : flowThreadPortionRect().x(); }