LayoutRect RenderMultiColumnSet::columnRectAt(unsigned index) const { LayoutUnit colLogicalWidth = computedColumnWidth(); LayoutUnit colLogicalHeight = computedColumnHeight(); LayoutUnit colLogicalTop = borderAndPaddingBefore(); LayoutUnit colLogicalLeft = borderAndPaddingLogicalLeft(); LayoutUnit colGap = columnGap(); RenderBlockFlow* parentFlow = toRenderBlockFlow(parent()); bool progressionReversed = parentFlow->multiColumnFlowThread()->progressionIsReversed(); bool progressionInline = parentFlow->multiColumnFlowThread()->progressionIsInline(); if (progressionInline) { if (style().isLeftToRightDirection() ^ progressionReversed) colLogicalLeft += index * (colLogicalWidth + colGap); else colLogicalLeft += contentLogicalWidth() - colLogicalWidth - index * (colLogicalWidth + colGap); } else { if (!progressionReversed) colLogicalTop += index * (colLogicalHeight + colGap); else colLogicalTop += contentLogicalHeight() - colLogicalHeight - index * (colLogicalHeight + colGap); } if (isHorizontalWritingMode()) return LayoutRect(colLogicalLeft, colLogicalTop, colLogicalWidth, colLogicalHeight); return LayoutRect(colLogicalTop, colLogicalLeft, colLogicalHeight, colLogicalWidth); }
IntRect computeTextBoundingBox(const RenderText& textRenderer, const Layout& layout) { auto resolver = lineResolver(toRenderBlockFlow(*textRenderer.parent()), layout); auto it = resolver.begin(); auto end = resolver.end(); if (it == end) return IntRect(); auto firstLineRect = *it; float left = firstLineRect.x(); float right = firstLineRect.maxX(); float bottom = firstLineRect.maxY(); for (++it; it != end; ++it) { auto rect = *it; if (rect.x() < left) left = rect.x(); if (rect.maxX() > right) right = rect.maxX(); if (rect.maxY() > bottom) bottom = rect.maxY(); } float x = left; float y = firstLineRect.y(); float width = right - left; float height = bottom - y; return enclosingIntRect(FloatRect(x, y, width, height)); }
static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer) { if (!renderer) return 0; if (renderer->isRenderBlockFlow()) { // If we're given a block element, it has to be a RenderSVGText. ASSERT(renderer->isSVGText()); RenderBlockFlow& renderBlock = toRenderBlockFlow(*renderer); // RenderSVGText only ever contains a single line box. InlineFlowBox* flowBox = renderBlock.firstLineBox(); ASSERT(flowBox == renderBlock.lastLineBox()); return flowBox; } if (renderer->isRenderInline()) { // We're given a RenderSVGInline or objects that derive from it (RenderSVGTSpan / RenderSVGTextPath) RenderInline& renderInline = toRenderInline(*renderer); // RenderSVGInline only ever contains a single line box. InlineFlowBox* flowBox = renderInline.firstLineBox(); ASSERT(flowBox == renderInline.lastLineBox()); return flowBox; } ASSERT_NOT_REACHED(); return 0; }
void RenderMultiColumnFlowThread::evacuateAndDestroy() { RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); m_beingEvacuated = true; // Delete the line box tree. deleteLines(); LayoutStateDisabler layoutStateDisabler(&view()); // First promote all children of the flow thread. Before we move them to the flow thread's // container, we need to unregister the flow thread, so that they aren't just re-added again to // the flow thread that we're trying to empty. multicolContainer->setMultiColumnFlowThread(nullptr); moveAllChildrenTo(multicolContainer, true); // Move spanners back to their original DOM position in the tree, and destroy the placeholders. SpannerMap::iterator it; while ((it = m_spannerMap.begin()) != m_spannerMap.end()) { RenderBox* spanner = it->key; RenderMultiColumnSpannerPlaceholder* placeholder = it->value; RenderBlockFlow* originalContainer = toRenderBlockFlow(placeholder->parent()); multicolContainer->removeChild(*spanner); originalContainer->addChild(spanner, placeholder); placeholder->destroy(); m_spannerMap.remove(it); } // Remove all sets. while (RenderMultiColumnSet* columnSet = firstMultiColumnSet()) columnSet->destroy(); destroy(); }
void RenderMultiColumnSet::prepareForLayout() { RenderBlockFlow* multicolBlock = toRenderBlockFlow(parent()); const RenderStyle& multicolStyle = multicolBlock->style(); // Set box logical top. ASSERT(!previousSiblingBox() || !previousSiblingBox()->isRenderMultiColumnSet()); // FIXME: multiple set not implemented; need to examine previous set to calculate the correct logical top. setLogicalTop(multicolBlock->borderAndPaddingBefore()); // Set box width. updateLogicalWidth(); if (multicolBlock->multiColumnFlowThread()->requiresBalancing()) { // Set maximum column height. We will not stretch beyond this. m_maxColumnHeight = RenderFlowThread::maxLogicalHeight(); if (!multicolStyle.logicalHeight().isAuto()) { m_maxColumnHeight = multicolBlock->computeContentLogicalHeight(multicolStyle.logicalHeight()); if (m_maxColumnHeight == -1) m_maxColumnHeight = RenderFlowThread::maxLogicalHeight(); } if (!multicolStyle.logicalMaxHeight().isUndefined()) { LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight(multicolStyle.logicalMaxHeight()); if (logicalMaxHeight != -1 && m_maxColumnHeight > logicalMaxHeight) m_maxColumnHeight = logicalMaxHeight; } m_maxColumnHeight = heightAdjustedForSetOffset(m_maxColumnHeight); m_computedColumnHeight = 0; // Restart balancing. } else setAndConstrainColumnHeight(heightAdjustedForSetOffset(multicolBlock->multiColumnFlowThread()->columnHeightAvailable())); clearForcedBreaks(); // Nuke previously stored minimum column height. Contents may have changed for all we know. m_minimumColumnHeight = 0; }
LayoutUnit RootInlineBox::selectionTopAdjustedForPrecedingBlock() const { const RootInlineBox& rootBox = root(); LayoutUnit top = selectionTop(); RenderObject::SelectionState blockSelectionState = rootBox.blockFlow().selectionState(); if (blockSelectionState != RenderObject::SelectionInside && blockSelectionState != RenderObject::SelectionEnd) return top; LayoutSize offsetToBlockBefore; if (RenderBlock* block = rootBox.blockFlow().blockBeforeWithinSelectionRoot(offsetToBlockBefore)) { if (block->isRenderBlockFlow()) { if (RootInlineBox* lastLine = toRenderBlockFlow(block)->lastRootBox()) { RenderObject::SelectionState lastLineSelectionState = lastLine->selectionState(); if (lastLineSelectionState != RenderObject::SelectionInside && lastLineSelectionState != RenderObject::SelectionStart) return top; LayoutUnit lastLineSelectionBottom = lastLine->selectionBottom() + offsetToBlockBefore.height(); top = std::max(top, lastLineSelectionBottom); } } } return top; }
IntPoint computeTextFirstRunLocation(const RenderText& textRenderer, const Layout& layout) { auto resolver = runResolver(toRenderBlockFlow(*textRenderer.parent()), layout); auto begin = resolver.begin(); if (begin == resolver.end()) return IntPoint(); return flooredIntPoint((*begin).rect().location()); }
LayoutUnit RenderMultiColumnSet::heightAdjustedForSetOffset(LayoutUnit height) const { RenderBlockFlow* multicolBlock = toRenderBlockFlow(parent()); LayoutUnit contentLogicalTop = logicalTop() - multicolBlock->borderAndPaddingBefore(); height -= contentLogicalTop; return std::max(height, LayoutUnit::fromPixel(1)); // Let's avoid zero height, as that would probably cause an infinite amount of columns to be created. }
LayoutUnit RenderMultiColumnSet::columnGap() const { // FIXME: Eventually we will cache the column gap when the widths of columns start varying, but for now we just // go to the parent block to get the gap. RenderBlockFlow* parentBlock = toRenderBlockFlow(parent()); if (parentBlock->style().hasNormalColumnGap()) return parentBlock->style().fontDescription().computedPixelSize(); // "1em" is recommended as the normal gap setting. Matches <p> margins. return parentBlock->style().columnGap(); }
void RenderMultiColumnSet::updateLogicalWidth() { RenderBlockFlow* parentBlock = toRenderBlockFlow(parent()); setComputedColumnWidthAndCount(parentBlock->multiColumnFlowThread()->columnWidth(), parentBlock->multiColumnFlowThread()->columnCount()); // FIXME: This will eventually vary if we are contained inside regions. // FIXME: When we add regions support, we'll start it off at the width of the multi-column // block in that particular region. setLogicalWidth(parentBox()->contentLogicalWidth()); }
void RenderMultiColumnSet::addForcedBreak(LayoutUnit offsetFromFirstPage) { if (!toRenderBlockFlow(parent())->multiColumnFlowThread()->requiresBalancing()) return; if (!m_contentRuns.isEmpty() && offsetFromFirstPage <= m_contentRuns.last().breakOffset()) return; // Append another item as long as we haven't exceeded used column count. What ends up in the // overflow area shouldn't affect column balancing. if (m_contentRuns.size() < m_computedColumnCount) m_contentRuns.append(ContentRun(offsetFromFirstPage)); }
Vector<IntRect> collectTextAbsoluteRects(const RenderText& textRenderer, const Layout& layout, const LayoutPoint& accumulatedOffset) { Vector<IntRect> rects; auto resolver = runResolver(toRenderBlockFlow(*textRenderer.parent()), layout); for (auto it = resolver.begin(), end = resolver.end(); it != end; ++it) { const auto& run = *it; auto rect = run.rect(); rects.append(enclosingIntRect(FloatRect(accumulatedOffset + rect.location(), rect.size()))); } return rects; }
Vector<FloatQuad> collectTextAbsoluteQuads(const RenderText& textRenderer, const Layout& layout, bool* wasFixed) { Vector<FloatQuad> quads; auto resolver = runResolver(toRenderBlockFlow(*textRenderer.parent()), layout); for (auto it = resolver.begin(), end = resolver.end(); it != end; ++it) { const auto& run = *it; auto rect = run.rect(); quads.append(textRenderer.localToAbsoluteQuad(FloatQuad(rect), 0, wasFixed)); } return quads; }
LayoutUnit RenderMultiColumnSet::initialBlockOffsetForPainting() const { RenderBlockFlow* parentFlow = toRenderBlockFlow(parent()); bool progressionReversed = parentFlow->multiColumnFlowThread()->progressionIsReversed(); bool progressionIsInline = parentFlow->multiColumnFlowThread()->progressionIsInline(); LayoutUnit result = 0; if (!progressionIsInline && progressionReversed) { LayoutRect colRect = columnRectAt(0); result = isHorizontalWritingMode() ? colRect.y() : colRect.x(); if (style().isFlippedBlocksWritingMode()) result = -result; } return result; }
void RenderMultiColumnSet::adjustRegionBoundsFromFlowThreadPortionRect(const LayoutPoint& layerOffset, LayoutRect& regionBounds) { LayoutUnit layerLogicalTop = isHorizontalWritingMode() ? layerOffset.y() : layerOffset.x(); unsigned startColumn = columnIndexAtOffset(layerLogicalTop); LayoutUnit colGap = columnGap(); LayoutUnit colLogicalWidth = computedColumnWidth(); LayoutRect flowThreadPortion = flowThreadPortionRectAt(startColumn); LayoutPoint translationOffset; RenderBlockFlow* parentFlow = toRenderBlockFlow(parent()); bool progressionReversed = parentFlow->multiColumnFlowThread()->progressionIsReversed(); bool progressionIsInline = parentFlow->multiColumnFlowThread()->progressionIsInline(); LayoutUnit initialBlockOffset = initialBlockOffsetForPainting(); LayoutUnit inlineOffset = progressionIsInline ? startColumn * (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 = startColumn * colGap; else blockOffset -= startColumn * (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. regionBounds.moveBy(roundedIntPoint(-translationOffset)); }
void RenderBoxModelObject::moveChildrenTo(RenderBoxModelObject* toBoxModelObject, RenderObject* startChild, RenderObject* endChild, RenderObject* beforeChild, bool fullRemoveInsert) { // This condition is rarely hit since this function is usually called on // anonymous blocks which can no longer carry positioned objects (see r120761) // or when fullRemoveInsert is false. if (fullRemoveInsert && isRenderBlock()) { RenderBlock* block = toRenderBlock(this); block->removePositionedObjects(0); if (block->isRenderBlockFlow()) toRenderBlockFlow(block)->removeFloatingObjects(); } ASSERT(!beforeChild || toBoxModelObject == beforeChild->parent()); for (RenderObject* child = startChild; child && child != endChild; ) { // Save our next sibling as moveChildTo will clear it. RenderObject* nextSibling = child->nextSibling(); moveChildTo(toBoxModelObject, child, beforeChild, fullRemoveInsert); child = nextSibling; } }
LayoutRect RenderMultiColumnSet::flowThreadPortionOverflowRect(const LayoutRect& portionRect, unsigned index, unsigned colCount, LayoutUnit colGap) { // 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. RenderBlockFlow* parentFlow = toRenderBlockFlow(parent()); bool progressionReversed = parentFlow->multiColumnFlowThread()->progressionIsReversed(); bool isFirstColumn = !index; bool isLastColumn = index == colCount - 1; bool isLeftmostColumn = style().isLeftToRightDirection() ^ progressionReversed ? isFirstColumn : isLastColumn; bool isRightmostColumn = style().isLeftToRightDirection() ^ progressionReversed ? isLastColumn : isFirstColumn; // Calculate the overflow rectangle, based on the flow thread's, clipped at column logical // top/bottom unless it's the first/last column. LayoutRect overflowRect = overflowRectForFlowThreadPortion(portionRect, isFirstColumn && isFirstRegion(), isLastColumn && isLastRegion(), VisualOverflow); // Avoid overflowing into neighboring columns, by clipping in the middle of adjacent column // gaps. Also make sure that we avoid rounding errors. if (isHorizontalWritingMode()) { if (!isLeftmostColumn) overflowRect.shiftXEdgeTo(portionRect.x() - colGap / 2); if (!isRightmostColumn) overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + colGap - colGap / 2); } else { if (!isLeftmostColumn) overflowRect.shiftYEdgeTo(portionRect.y() - colGap / 2); if (!isRightmostColumn) overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + colGap - colGap / 2); } return overflowRect; }
static PassRefPtr<InspectorObject> buildObjectForRegionHighlight(FrameView* mainView, RenderRegion* region) { FrameView* containingView = region->frame().view(); if (!containingView) return nullptr; RenderBlockFlow* regionContainer = toRenderBlockFlow(region->parent()); LayoutRect borderBox = regionContainer->borderBoxRect(); borderBox.setWidth(borderBox.width() + regionContainer->verticalScrollbarWidth()); borderBox.setHeight(borderBox.height() + regionContainer->horizontalScrollbarHeight()); // Create incoming and outgoing boxes that we use to chain the regions toghether. const LayoutSize linkBoxSize(10, 10); const LayoutSize linkBoxMidpoint(linkBoxSize.width() / 2, linkBoxSize.height() / 2); LayoutRect incomingRectBox = LayoutRect(borderBox.location() - linkBoxMidpoint, linkBoxSize); LayoutRect outgoingRectBox = LayoutRect(borderBox.location() - linkBoxMidpoint + borderBox.size(), linkBoxSize); // Move the link boxes slightly inside the region border box. LayoutUnit maxUsableHeight = std::max(LayoutUnit(), borderBox.height() - linkBoxMidpoint.height()); LayoutUnit linkBoxVerticalOffset = std::min(LayoutUnit::fromPixel(15), maxUsableHeight); incomingRectBox.move(0, linkBoxVerticalOffset); outgoingRectBox.move(0, -linkBoxVerticalOffset); FloatQuad borderRectQuad = regionContainer->localToAbsoluteQuad(FloatRect(borderBox)); FloatQuad incomingRectQuad = regionContainer->localToAbsoluteQuad(FloatRect(incomingRectBox)); FloatQuad outgoingRectQuad = regionContainer->localToAbsoluteQuad(FloatRect(outgoingRectBox)); contentsQuadToPage(mainView, containingView, borderRectQuad); contentsQuadToPage(mainView, containingView, incomingRectQuad); contentsQuadToPage(mainView, containingView, outgoingRectQuad); RefPtr<InspectorObject> regionObject = InspectorObject::create(); regionObject->setArray("borderQuad", buildArrayForQuad(borderRectQuad)); regionObject->setArray("incomingQuad", buildArrayForQuad(incomingRectQuad)); regionObject->setArray("outgoingQuad", buildArrayForQuad(outgoingRectQuad)); return regionObject.release(); }
void RenderMultiColumnFlowThread::autoGenerateRegionsToBlockOffset(LayoutUnit /*offset*/) { // This function ensures we have the correct column set information at all times. // 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 element. // // 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. RenderMultiColumnSet* firstSet = toRenderMultiColumnSet(firstRegion()); if (firstSet) return; invalidateRegions(); RenderBlockFlow* parentBlock = toRenderBlockFlow(parent()); firstSet = new RenderMultiColumnSet(*this, RenderStyle::createAnonymousStyleWithDisplay(&parentBlock->style(), BLOCK)); firstSet->initializeStyle(); parentBlock->RenderBlock::addChild(firstSet); // Even though we aren't placed yet, we can go ahead and set up our size. At this point we're // typically in the middle of laying out the thread, attempting to paginate, and we need to do // some rudimentary "layout" of the set now, so that pagination will work. firstSet->prepareForLayout(); validateRegions(); }
bool RenderMultiColumnSet::recalculateBalancedHeight(bool initial) { ASSERT(toRenderBlockFlow(parent())->multiColumnFlowThread()->requiresBalancing()); LayoutUnit oldColumnHeight = m_computedColumnHeight; if (initial) distributeImplicitBreaks(); LayoutUnit newColumnHeight = calculateBalancedHeight(initial); setAndConstrainColumnHeight(newColumnHeight); // After having calculated an initial column height, the multicol container typically needs at // least one more layout pass with a new column height, but if a height was specified, we only // need to do this if we think that we need less space than specified. Conversely, if we // determined that the columns need to be as tall as the specified height of the container, we // have already laid it out correctly, and there's no need for another pass. if (m_computedColumnHeight == oldColumnHeight) return false; // No change. We're done. m_minSpaceShortage = RenderFlowThread::maxLogicalHeight(); clearForcedBreaks(); return true; // Need another pass. }
void RenderMultiColumnFlowThread::flowThreadDescendantInserted(RenderObject* descendant) { if (gShiftingSpanner || m_beingEvacuated || descendant->isInFlowRenderFlowThread()) return; RenderObject* subtreeRoot = descendant; for (; descendant; descendant = (descendant ? descendant->nextInPreOrder(subtreeRoot) : nullptr)) { if (descendant->isRenderMultiColumnSpannerPlaceholder()) { // A spanner's placeholder has been inserted. The actual spanner renderer is moved from // where it would otherwise occur (if it weren't a spanner) to becoming a sibling of the // column sets. RenderMultiColumnSpannerPlaceholder* placeholder = toRenderMultiColumnSpannerPlaceholder(descendant); if (placeholder->flowThread() != this) { // This isn't our spanner! It shifted here from an ancestor multicolumn block. It's going to end up // becoming our spanner instead, but for it to do that we first have to nuke the original spanner, // and get the spanner content back into this flow thread. RenderBox* spanner = placeholder->spanner(); // Get info for the move of the original content back into our flow thread. RenderBoxModelObject* placeholderParent = toRenderBoxModelObject(placeholder->parent()); // We have to nuke the placeholder, since the ancestor already lost the mapping to it when // we shifted the placeholder down into this flow thread. RenderObject* placeholderNextSibling = placeholderParent->removeChild(*placeholder); // Get the ancestor multicolumn flow thread to clean up its mess. RenderBlockFlow* ancestorBlock = toRenderBlockFlow(spanner->parent()); ancestorBlock->multiColumnFlowThread()->flowThreadRelativeWillBeRemoved(spanner); // Now move the original content into our flow thread. It will end up calling flowThreadDescendantInserted // on the new content only, and everything will get set up properly. ancestorBlock->moveChildTo(placeholderParent, spanner, placeholderNextSibling, true); // Advance descendant. descendant = placeholderNextSibling; // If the spanner was the subtree root, then we're done, since there is nothing else left to insert. if (!descendant) return; // Now that we have done this, we can continue past the spanning content, since we advanced // descendant already. if (descendant) descendant = descendant->previousInPreOrder(subtreeRoot); continue; } ASSERT(!m_spannerMap.get(placeholder->spanner())); m_spannerMap.add(placeholder->spanner(), placeholder); ASSERT(!placeholder->firstChild()); // There should be no children here, but if there are, we ought to skip them. continue; } RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); RenderObject* nextRendererInFlowThread = descendant->nextInPreOrderAfterChildren(this); RenderObject* insertBeforeMulticolChild = nullptr; if (isValidColumnSpanner(this, descendant)) { // This is a spanner (column-span:all). Such renderers are moved from where they would // otherwise occur in the render tree to becoming a direct child of the multicol container, // so that they live among the column sets. This simplifies the layout implementation, and // basically just relies on regular block layout done by the RenderBlockFlow that // establishes the multicol container. RenderBlockFlow* container = toRenderBlockFlow(descendant->parent()); RenderMultiColumnSet* setToSplit = nullptr; if (nextRendererInFlowThread) { setToSplit = findSetRendering(descendant); if (setToSplit) { setToSplit->setNeedsLayout(); insertBeforeMulticolChild = setToSplit->nextSibling(); } } // Moving a spanner's renderer so that it becomes a sibling of the column sets requires us // to insert an anonymous placeholder in the tree where the spanner's renderer otherwise // would have been. This is needed for a two reasons: We need a way of separating inline // content before and after the spanner, so that it becomes separate line boxes. Secondly, // this placeholder serves as a break point for column sets, so that, when encountered, we // end flowing one column set and move to the next one. RenderMultiColumnSpannerPlaceholder* placeholder = RenderMultiColumnSpannerPlaceholder::createAnonymous(this, toRenderBox(descendant), &container->style()); container->addChild(placeholder, descendant->nextSibling()); container->removeChild(*descendant); // This is a guard to stop an ancestor flow thread from processing the spanner. gShiftingSpanner = true; multicolContainer->RenderBlock::addChild(descendant, insertBeforeMulticolChild); gShiftingSpanner = false; // The spanner has now been moved out from the flow thread, but we don't want to // examine its children anyway. They are all part of the spanner and shouldn't trigger // creation of column sets or anything like that. Continue at its original position in // the tree, i.e. where the placeholder was just put. if (subtreeRoot == descendant) subtreeRoot = placeholder; descendant = placeholder; } else { // This is regular multicol content, i.e. not part of a spanner. if (nextRendererInFlowThread && nextRendererInFlowThread->isRenderMultiColumnSpannerPlaceholder()) { // Inserted right before a spanner. Is there a set for us there? RenderMultiColumnSpannerPlaceholder* placeholder = toRenderMultiColumnSpannerPlaceholder(nextRendererInFlowThread); if (RenderObject* previous = placeholder->spanner()->previousSibling()) { if (previous->isRenderMultiColumnSet()) continue; // There's already a set there. Nothing to do. } insertBeforeMulticolChild = placeholder->spanner(); } else if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) { // This child is not an immediate predecessor of a spanner, which means that if this // child precedes a spanner at all, there has to be a column set created for us there // already. If it doesn't precede any spanner at all, on the other hand, we need a // column set at the end of the multicol container. We don't really check here if the // child inserted precedes any spanner or not (as that's an expensive operation). Just // make sure we have a column set at the end. It's no big deal if it remains unused. if (!lastSet->nextSibling()) continue; } } // Need to create a new column set when there's no set already created. We also always insert // another column set after a spanner. Even if it turns out that there are no renderers // following the spanner, there may be bottom margins there, which take up space. RenderMultiColumnSet* newSet = new RenderMultiColumnSet(*this, RenderStyle::createAnonymousStyleWithDisplay(&multicolContainer->style(), BLOCK)); newSet->initializeStyle(); multicolContainer->RenderBlock::addChild(newSet, insertBeforeMulticolChild); invalidateRegions(); // We cannot handle immediate column set siblings at the moment (and there's no need for // it, either). There has to be at least one spanner separating them. ASSERT(!previousColumnSetOrSpannerSiblingOf(newSet) || !previousColumnSetOrSpannerSiblingOf(newSet)->isRenderMultiColumnSet()); ASSERT(!nextColumnSetOrSpannerSiblingOf(newSet) || !nextColumnSetOrSpannerSiblingOf(newSet)->isRenderMultiColumnSet()); } }
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); } }
static PassRefPtr<InspectorObject> buildObjectForElementInfo(Node* node) { if (!node->isElementNode() || !node->document().frame()) return nullptr; RefPtr<InspectorObject> elementInfo = InspectorObject::create(); Element* element = toElement(node); bool isXHTML = element->document().isXHTMLDocument(); elementInfo->setString("tagName", isXHTML ? element->nodeName() : element->nodeName().lower()); elementInfo->setString("idValue", element->getIdAttribute()); HashSet<AtomicString> usedClassNames; if (element->hasClass() && element->isStyledElement()) { StringBuilder classNames; const SpaceSplitString& classNamesString = toStyledElement(element)->classNames(); size_t classNameCount = classNamesString.size(); for (size_t i = 0; i < classNameCount; ++i) { const AtomicString& className = classNamesString[i]; if (usedClassNames.contains(className)) continue; usedClassNames.add(className); classNames.append('.'); classNames.append(className); } elementInfo->setString("className", classNames.toString()); } RenderElement* renderer = element->renderer(); Frame* containingFrame = node->document().frame(); FrameView* containingView = containingFrame->view(); IntRect boundingBox = pixelSnappedIntRect(containingView->contentsToRootView(renderer->absoluteBoundingBoxRect())); RenderBoxModelObject* modelObject = renderer->isBoxModelObject() ? toRenderBoxModelObject(renderer) : nullptr; elementInfo->setString("nodeWidth", String::number(modelObject ? adjustForAbsoluteZoom(modelObject->pixelSnappedOffsetWidth(), *modelObject) : boundingBox.width())); elementInfo->setString("nodeHeight", String::number(modelObject ? adjustForAbsoluteZoom(modelObject->pixelSnappedOffsetHeight(), *modelObject) : boundingBox.height())); if (renderer->isRenderNamedFlowFragmentContainer()) { RenderNamedFlowFragment* region = toRenderBlockFlow(renderer)->renderNamedFlowFragment(); if (region->isValid()) { RenderFlowThread* flowThread = region->flowThread(); ASSERT(flowThread && flowThread->isRenderNamedFlowThread()); RefPtr<InspectorObject> regionFlowInfo = InspectorObject::create(); regionFlowInfo->setString("name", toRenderNamedFlowThread(flowThread)->flowThreadName()); regionFlowInfo->setArray("regions", buildObjectForCSSRegionsHighlight(region, flowThread)); elementInfo->setObject("regionFlowInfo", regionFlowInfo.release()); } } RenderFlowThread* containingFlowThread = renderer->flowThreadContainingBlock(); if (containingFlowThread && containingFlowThread->isRenderNamedFlowThread()) { RefPtr<InspectorObject> contentFlowInfo = InspectorObject::create(); contentFlowInfo->setString("name", toRenderNamedFlowThread(containingFlowThread)->flowThreadName()); elementInfo->setObject("contentFlowInfo", contentFlowInfo.release()); } #if ENABLE(CSS_SHAPES) if (renderer->isBox()) { RenderBox* renderBox = toRenderBox(renderer); if (RefPtr<InspectorObject> shapeObject = buildObjectForShapeOutside(containingFrame, renderBox)) elementInfo->setObject("shapeOutsideInfo", shapeObject.release()); } #endif // Need to enable AX to get the computed role. if (!WebCore::AXObjectCache::accessibilityEnabled()) WebCore::AXObjectCache::enableAccessibility(); if (AXObjectCache* axObjectCache = node->document().axObjectCache()) { if (AccessibilityObject* axObject = axObjectCache->getOrCreate(node)) elementInfo->setString("role", axObject->computedRoleString()); } return elementInfo.release(); }
RenderBlockFlow& RootInlineBox::blockFlow() const { return toRenderBlockFlow(renderer()); }
void RenderMultiColumnSet::paintColumnRules(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (paintInfo.context->paintingDisabled()) return; RenderMultiColumnFlowThread* flowThread = toRenderBlockFlow(parent())->multiColumnFlowThread(); const RenderStyle& blockStyle = parent()->style(); const Color& ruleColor = blockStyle.visitedDependentColor(CSSPropertyWebkitColumnRuleColor); bool ruleTransparent = blockStyle.columnRuleIsTransparent(); EBorderStyle ruleStyle = blockStyle.columnRuleStyle(); LayoutUnit ruleThickness = blockStyle.columnRuleWidth(); LayoutUnit colGap = columnGap(); bool renderRule = ruleStyle > BHIDDEN && !ruleTransparent; if (!renderRule) return; unsigned colCount = columnCount(); if (colCount <= 1) return; bool antialias = shouldAntialiasLines(paintInfo.context); if (flowThread->progressionIsInline()) { bool leftToRight = style().isLeftToRightDirection() ^ flowThread->progressionIsReversed(); LayoutUnit currLogicalLeftOffset = leftToRight ? LayoutUnit() : contentLogicalWidth(); LayoutUnit ruleAdd = logicalLeftOffsetForContent(); LayoutUnit ruleLogicalLeft = leftToRight ? LayoutUnit() : contentLogicalWidth(); LayoutUnit inlineDirectionSize = computedColumnWidth(); BoxSide boxSide = isHorizontalWritingMode() ? leftToRight ? BSLeft : BSRight : leftToRight ? BSTop : BSBottom; for (unsigned i = 0; i < colCount; i++) { // Move to the next position. if (leftToRight) { ruleLogicalLeft += inlineDirectionSize + colGap / 2; currLogicalLeftOffset += inlineDirectionSize + colGap; } else { ruleLogicalLeft -= (inlineDirectionSize + colGap / 2); currLogicalLeftOffset -= (inlineDirectionSize + colGap); } // Now paint the column rule. if (i < colCount - 1) { LayoutUnit ruleLeft = isHorizontalWritingMode() ? paintOffset.x() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd : paintOffset.x() + borderLeft() + paddingLeft(); LayoutUnit ruleRight = isHorizontalWritingMode() ? ruleLeft + ruleThickness : ruleLeft + contentWidth(); LayoutUnit ruleTop = isHorizontalWritingMode() ? paintOffset.y() + borderTop() + paddingTop() : paintOffset.y() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd; LayoutUnit ruleBottom = isHorizontalWritingMode() ? ruleTop + contentHeight() : ruleTop + ruleThickness; IntRect pixelSnappedRuleRect = pixelSnappedIntRectFromEdges(ruleLeft, ruleTop, ruleRight, ruleBottom); drawLineForBoxSide(paintInfo.context, pixelSnappedRuleRect.x(), pixelSnappedRuleRect.y(), pixelSnappedRuleRect.maxX(), pixelSnappedRuleRect.maxY(), boxSide, ruleColor, ruleStyle, 0, 0, antialias); } ruleLogicalLeft = currLogicalLeftOffset; } } else { bool topToBottom = !style().isFlippedBlocksWritingMode() ^ flowThread->progressionIsReversed(); LayoutUnit ruleLeft = isHorizontalWritingMode() ? LayoutUnit() : colGap / 2 - colGap - ruleThickness / 2; LayoutUnit ruleWidth = isHorizontalWritingMode() ? contentWidth() : ruleThickness; LayoutUnit ruleTop = isHorizontalWritingMode() ? colGap / 2 - colGap - ruleThickness / 2 : LayoutUnit(); LayoutUnit ruleHeight = isHorizontalWritingMode() ? ruleThickness : contentHeight(); LayoutRect ruleRect(ruleLeft, ruleTop, ruleWidth, ruleHeight); if (!topToBottom) { if (isHorizontalWritingMode()) ruleRect.setY(height() - ruleRect.maxY()); else ruleRect.setX(width() - ruleRect.maxX()); } ruleRect.moveBy(paintOffset); BoxSide boxSide = isHorizontalWritingMode() ? topToBottom ? BSTop : BSBottom : topToBottom ? BSLeft : BSRight; LayoutSize step(0, topToBottom ? computedColumnHeight() + colGap : -(computedColumnHeight() + colGap)); if (!isHorizontalWritingMode()) step = step.transposedSize(); for (unsigned i = 1; i < colCount; i++) { ruleRect.move(step); IntRect pixelSnappedRuleRect = pixelSnappedIntRect(ruleRect); drawLineForBoxSide(paintInfo.context, pixelSnappedRuleRect.x(), pixelSnappedRuleRect.y(), pixelSnappedRuleRect.maxX(), pixelSnappedRuleRect.maxY(), boxSide, ruleColor, ruleStyle, 0, 0, antialias); } } }
RenderLayer* RenderRegion::regionContainerLayer() const { ASSERT(parent() && parent()->isRenderNamedFlowFragmentContainer()); return toRenderBlockFlow(parent())->layer(); }
LayoutState::LayoutState(std::unique_ptr<LayoutState> next, RenderBox* renderer, const LayoutSize& offset, LayoutUnit pageLogicalHeight, bool pageLogicalHeightChanged, ColumnInfo* columnInfo) : m_columnInfo(columnInfo) , m_lineGrid(0) , m_next(std::move(next)) #if ENABLE(CSS_SHAPES) , m_shapeInsideInfo(0) #endif #ifndef NDEBUG , m_renderer(renderer) #endif { ASSERT(m_next); bool fixed = renderer->isOutOfFlowPositioned() && renderer->style().position() == FixedPosition; if (fixed) { // FIXME: This doesn't work correctly with transforms. FloatPoint fixedOffset = renderer->view().localToAbsolute(FloatPoint(), IsFixed); m_paintOffset = LayoutSize(fixedOffset.x(), fixedOffset.y()) + offset; } else m_paintOffset = m_next->m_paintOffset + offset; if (renderer->isOutOfFlowPositioned() && !fixed) { if (RenderElement* container = renderer->container()) { if (container->isInFlowPositioned() && container->isRenderInline()) m_paintOffset += toRenderInline(container)->offsetForInFlowPositionedInline(renderer); } } m_layoutOffset = m_paintOffset; if (renderer->isInFlowPositioned() && renderer->hasLayer()) m_paintOffset += renderer->layer()->offsetForInFlowPosition(); m_clipped = !fixed && m_next->m_clipped; if (m_clipped) m_clipRect = m_next->m_clipRect; if (renderer->hasOverflowClip()) { LayoutRect clipRect(toPoint(m_paintOffset) + renderer->view().layoutDelta(), renderer->cachedSizeForOverflowClip()); if (m_clipped) m_clipRect.intersect(clipRect); else { m_clipRect = clipRect; m_clipped = true; } m_paintOffset -= renderer->scrolledContentOffset(); } // If we establish a new page height, then cache the offset to the top of the first page. // We can compare this later on to figure out what part of the page we're actually on, if (pageLogicalHeight || m_columnInfo || renderer->isRenderFlowThread()) { m_pageLogicalHeight = pageLogicalHeight; bool isFlipped = renderer->style().isFlippedBlocksWritingMode(); m_pageOffset = LayoutSize(m_layoutOffset.width() + (!isFlipped ? renderer->borderLeft() + renderer->paddingLeft() : renderer->borderRight() + renderer->paddingRight()), m_layoutOffset.height() + (!isFlipped ? renderer->borderTop() + renderer->paddingTop() : renderer->borderBottom() + renderer->paddingBottom())); m_pageLogicalHeightChanged = pageLogicalHeightChanged; } else { // If we don't establish a new page height, then propagate the old page height and offset down. m_pageLogicalHeight = m_next->m_pageLogicalHeight; m_pageLogicalHeightChanged = m_next->m_pageLogicalHeightChanged; m_pageOffset = m_next->m_pageOffset; // Disable pagination for objects we don't support. For now this includes overflow:scroll/auto, inline blocks and // writing mode roots. if (renderer->isUnsplittableForPagination()) m_pageLogicalHeight = 0; } // Propagate line grid information. propagateLineGridInfo(renderer); if (!m_columnInfo) m_columnInfo = m_next->m_columnInfo; #if ENABLE(CSS_SHAPES) if (renderer->isRenderBlock()) { const RenderBlock* renderBlock = toRenderBlock(renderer); m_shapeInsideInfo = renderBlock->shapeInsideInfo(); if (!m_shapeInsideInfo && m_next->m_shapeInsideInfo && renderBlock->allowsShapeInsideInfoSharing()) m_shapeInsideInfo = m_next->m_shapeInsideInfo; } #endif m_layoutDelta = m_next->m_layoutDelta; #if !ASSERT_DISABLED && ENABLE(SATURATED_LAYOUT_ARITHMETIC) m_layoutDeltaXSaturated = m_next->m_layoutDeltaXSaturated; m_layoutDeltaYSaturated = m_next->m_layoutDeltaYSaturated; #endif m_isPaginated = m_pageLogicalHeight || m_columnInfo || renderer->isRenderFlowThread(); if (lineGrid() && renderer->hasColumns() && renderer->style().hasInlineColumnAxis()) computeLineGridPaginationOrigin(renderer); // If we have a new grid to track, then add it to our set. if (renderer->style().lineGrid() != RenderStyle::initialLineGrid() && renderer->isRenderBlockFlow()) establishLineGrid(toRenderBlockFlow(renderer)); // FIXME: <http://bugs.webkit.org/show_bug.cgi?id=13443> Apply control clip if present. }
RenderBlockFlow& RenderNamedFlowFragment::fragmentContainer() const { ASSERT(parent()); ASSERT(parent()->isRenderNamedFlowFragmentContainer()); return *toRenderBlockFlow(parent()); }