void RenderMathMLOperator::paint(PaintInfo& info, const LayoutPoint& paintOffset) { RenderMathMLBlock::paint(info, paintOffset); if (info.context->paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != VISIBLE) return; if (!m_isStretched && !m_stretchyCharacter) { RenderMathMLBlock::paint(info, paintOffset); return; } GraphicsContextStateSaver stateSaver(*info.context); info.context->setFillColor(style().visitedDependentColor(CSSPropertyColor), style().colorSpace()); ASSERT(m_stretchyCharacter->topGlyph); ASSERT(m_stretchyCharacter->bottomGlyph); // We are positioning the glyphs so that the edge of the tight glyph bounds line up exactly with the edges of our paint box. LayoutPoint operatorTopLeft = ceiledIntPoint(paintOffset + location()); FloatRect topGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->topGlyph); LayoutPoint topGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() - topGlyphBounds.y()); LayoutRect topGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->topGlyph, topGlyphOrigin, TrimBottom); FloatRect bottomGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->bottomGlyph); LayoutPoint bottomGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() + offsetHeight() - (bottomGlyphBounds.height() + bottomGlyphBounds.y())); LayoutRect bottomGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->bottomGlyph, bottomGlyphOrigin, TrimTop); if (m_stretchyCharacter->middleGlyph) { // Center the glyph origin between the start and end glyph paint extents. Then shift it half the paint height toward the bottom glyph. FloatRect middleGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->middleGlyph); LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), topGlyphOrigin.y()); middleGlyphOrigin.moveBy(LayoutPoint(0, (bottomGlyphPaintRect.y() - topGlyphPaintRect.maxY()) / 2.0)); middleGlyphOrigin.moveBy(LayoutPoint(0, middleGlyphBounds.height() / 2.0)); LayoutRect middleGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->middleGlyph, middleGlyphOrigin, TrimTopAndBottom); fillWithExtensionGlyph(info, topGlyphPaintRect.minXMaxYCorner(), middleGlyphPaintRect.minXMinYCorner()); fillWithExtensionGlyph(info, middleGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner()); } else fillWithExtensionGlyph(info, topGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner()); }
bool RenderReplaced::shouldPaint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseOutline && paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseMask) return false; if (!paintInfo.shouldPaintWithinRoot(*this)) return false; // if we're invisible or haven't received a layout yet, then just bail. if (style().visibility() != VISIBLE) return false; RenderNamedFlowFragment* namedFlowFragment = currentRenderNamedFlowFragment(); // Check our region range to make sure we need to be painting in this region. if (namedFlowFragment && !namedFlowFragment->flowThread()->objectShouldFragmentInFlowRegion(this, namedFlowFragment)) return false; LayoutPoint adjustedPaintOffset = paintOffset + location(); // Early exit if the element touches the edges. LayoutUnit top = adjustedPaintOffset.y() + visualOverflowRect().y(); LayoutUnit bottom = adjustedPaintOffset.y() + visualOverflowRect().maxY(); if (isSelected() && m_inlineBoxWrapper) { const RootInlineBox& rootBox = m_inlineBoxWrapper->root(); LayoutUnit selTop = paintOffset.y() + rootBox.selectionTop(); LayoutUnit selBottom = paintOffset.y() + selTop + rootBox.selectionHeight(); top = std::min(selTop, top); bottom = std::max(selBottom, bottom); } LayoutRect localRepaintRect = paintInfo.rect; adjustRectWithMaximumOutline(paintInfo.phase, localRepaintRect); if (adjustedPaintOffset.x() + visualOverflowRect().x() >= localRepaintRect.maxX() || adjustedPaintOffset.x() + visualOverflowRect().maxX() <= localRepaintRect.x()) return false; if (top >= localRepaintRect.maxY() || bottom <= localRepaintRect.y()) return false; return true; }
LayoutRect RootInlineBox::paddedLayoutOverflowRect(LayoutUnit endPadding) const { LayoutRect lineLayoutOverflow = layoutOverflowRect(lineTop(), lineBottom()); if (!endPadding) return lineLayoutOverflow; // FIXME: Audit whether to use pixel snapped values when not using integers for layout: https://bugs.webkit.org/show_bug.cgi?id=63656 if (isHorizontal()) { if (isLeftToRightDirection()) lineLayoutOverflow.shiftMaxXEdgeTo(std::max<LayoutUnit>(lineLayoutOverflow.maxX(), pixelSnappedLogicalRight() + endPadding)); else lineLayoutOverflow.shiftXEdgeTo(std::min<LayoutUnit>(lineLayoutOverflow.x(), pixelSnappedLogicalLeft() - endPadding)); } else { if (isLeftToRightDirection()) lineLayoutOverflow.shiftMaxYEdgeTo(std::max<LayoutUnit>(lineLayoutOverflow.maxY(), pixelSnappedLogicalRight() + endPadding)); else lineLayoutOverflow.shiftYEdgeTo(std::min<LayoutUnit>(lineLayoutOverflow.y(), pixelSnappedLogicalLeft() - endPadding)); } return lineLayoutOverflow; }
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; }
LayoutRect MultiColumnFragmentainerGroup::flowThreadPortionOverflowRectAt(unsigned columnIndex) 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. bool isFirstColumnInRow = !columnIndex; bool isLastColumnInRow = columnIndex == actualColumnCount() - 1; bool isLTR = m_columnSet.style()->isLeftToRightDirection(); bool isLeftmostColumn = isLTR ? isFirstColumnInRow : isLastColumnInRow; bool isRightmostColumn = isLTR ? isLastColumnInRow : isFirstColumnInRow; LayoutRect portionRect = flowThreadPortionRectAt(columnIndex); bool isFirstColumnInMulticolContainer = isFirstColumnInRow && this == &m_columnSet.firstFragmentainerGroup() && !m_columnSet.previousSiblingMultiColumnSet(); bool isLastColumnInMulticolContainer = isLastColumnInRow && this == &m_columnSet.lastFragmentainerGroup() && !m_columnSet.nextSiblingMultiColumnSet(); // 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 = m_columnSet.overflowRectForFlowThreadPortion(portionRect, isFirstColumnInMulticolContainer, isLastColumnInMulticolContainer); // Avoid overflowing into neighboring columns, by clipping in the middle of adjacent column // gaps. Also make sure that we avoid rounding errors. LayoutUnit columnGap = m_columnSet.columnGap(); if (m_columnSet.isHorizontalWritingMode()) { if (!isLeftmostColumn) overflowRect.shiftXEdgeTo(portionRect.x() - columnGap / 2); if (!isRightmostColumn) overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + columnGap - columnGap / 2); } else { if (!isLeftmostColumn) overflowRect.shiftYEdgeTo(portionRect.y() - columnGap / 2); if (!isRightmostColumn) overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + columnGap - columnGap / 2); } return overflowRect; }
bool RenderReplaced::shouldPaint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseOutline && paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseMask) return false; if (!paintInfo.shouldPaintWithinRoot(*this)) return false; // if we're invisible or haven't received a layout yet, then just bail. if (style().visibility() != VISIBLE) return false; LayoutPoint adjustedPaintOffset = paintOffset + location(); // Early exit if the element touches the edges. LayoutUnit top = adjustedPaintOffset.y() + visualOverflowRect().y(); LayoutUnit bottom = adjustedPaintOffset.y() + visualOverflowRect().maxY(); if (isSelected() && m_inlineBoxWrapper) { const RootInlineBox& rootBox = m_inlineBoxWrapper->root(); LayoutUnit selTop = paintOffset.y() + rootBox.selectionTop(); LayoutUnit selBottom = paintOffset.y() + selTop + rootBox.selectionHeight(); top = std::min(selTop, top); bottom = std::max(selBottom, bottom); } LayoutRect localRepaintRect = paintInfo.rect; localRepaintRect.inflate(maximalOutlineSize(paintInfo.phase)); if (adjustedPaintOffset.x() + visualOverflowRect().x() >= localRepaintRect.maxX() || adjustedPaintOffset.x() + visualOverflowRect().maxX() <= localRepaintRect.x()) return false; if (top >= localRepaintRect.maxY() || bottom <= localRepaintRect.y()) return false; return true; }
void LayoutMultiColumnFlowThread::layout() { ASSERT(!m_lastSetWorkedOn); m_lastSetWorkedOn = firstMultiColumnSet(); if (m_lastSetWorkedOn) m_lastSetWorkedOn->beginFlow(LayoutUnit()); LayoutFlowThread::layout(); if (LayoutMultiColumnSet* lastSet = lastMultiColumnSet()) { ASSERT(lastSet == m_lastSetWorkedOn); if (!lastSet->nextSiblingMultiColumnBox()) { // Include trailing overflow in the last column set. 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. // TODO(mstensho): Once we support nested multicol, adding in overflow here may result // in the need for creating additional rows, since there may not be enough space // remaining in the currently last row. LayoutRect layoutRect = layoutOverflowRect(); LayoutUnit logicalBottomInFlowThread = isHorizontalWritingMode() ? layoutRect.maxY() : layoutRect.maxX(); ASSERT(logicalBottomInFlowThread >= logicalHeight()); lastSet->endFlow(logicalBottomInFlowThread); } } m_lastSetWorkedOn = nullptr; }
// The starting rect is the rect of the focused node, in document coordinates. // Compose a virtual starting rect if there is no focused node or if it is off screen. // The virtual rect is the edge of the container or frame. We select which // edge depending on the direction of the navigation. LayoutRect virtualRectForDirection(FocusType type, const LayoutRect& startingRect, LayoutUnit width) { LayoutRect virtualStartingRect = startingRect; switch (type) { case FocusTypeLeft: virtualStartingRect.setX(virtualStartingRect.maxX() - width); virtualStartingRect.setWidth(width); break; case FocusTypeUp: virtualStartingRect.setY(virtualStartingRect.maxY() - width); virtualStartingRect.setHeight(width); break; case FocusTypeRight: virtualStartingRect.setWidth(width); break; case FocusTypeDown: virtualStartingRect.setHeight(width); break; default: ASSERT_NOT_REACHED(); } return virtualStartingRect; }
bool LineBoxList::rangeIntersectsRect(LayoutBoxModelObject* renderer, LayoutUnit logicalTop, LayoutUnit logicalBottom, const LayoutRect& rect, const LayoutPoint& offset) const { LayoutBox* block; if (renderer->isBox()) block = toLayoutBox(renderer); else block = renderer->containingBlock(); LayoutUnit physicalStart = block->flipForWritingMode(logicalTop); LayoutUnit physicalEnd = block->flipForWritingMode(logicalBottom); LayoutUnit physicalExtent = absoluteValue(physicalEnd - physicalStart); physicalStart = std::min(physicalStart, physicalEnd); if (renderer->style()->isHorizontalWritingMode()) { physicalStart += offset.y(); if (physicalStart >= rect.maxY() || physicalStart + physicalExtent <= rect.y()) return false; } else { physicalStart += offset.x(); if (physicalStart >= rect.maxX() || physicalStart + physicalExtent <= rect.x()) return false; } return true; }
IntRect enclosingIntRect(const LayoutRect& rect) { // Empty rects with fractional x, y values turn into non-empty rects when converting to enclosing. // We need to ensure that empty rects stay empty after the conversion, because the selection code expects them to be empty. IntPoint location = flooredIntPoint(rect.minXMinYCorner()); IntPoint maxPoint = IntPoint(rect.width() ? rect.maxX().ceil() : location.x(), rect.height() ? rect.maxY().ceil() : location.y()); return IntRect(location, maxPoint - location); }
void RenderLineBreak::collectSelectionRects(Vector<SelectionRect>& rects, unsigned, unsigned) { ensureLineBoxes(*this); InlineElementBox* box = m_inlineBoxWrapper; if (!box) return; const RootInlineBox& rootBox = box->root(); LayoutRect rect = rootBox.computeCaretRect(box->logicalLeft(), 0, nullptr); if (rootBox.isFirstAfterPageBreak()) { if (box->isHorizontal()) rect.shiftYEdgeTo(rootBox.lineTopWithLeading()); else rect.shiftXEdgeTo(rootBox.lineTopWithLeading()); } RenderBlock* containingBlock = this->containingBlock(); // Map rect, extended left to leftOffset, and right to rightOffset, through transforms to get minX and maxX. LogicalSelectionOffsetCaches cache(*containingBlock); LayoutUnit leftOffset = containingBlock->logicalLeftSelectionOffset(*containingBlock, box->logicalTop(), cache); LayoutUnit rightOffset = containingBlock->logicalRightSelectionOffset(*containingBlock, box->logicalTop(), cache); LayoutRect extentsRect = rect; if (box->isHorizontal()) { extentsRect.setX(leftOffset); extentsRect.setWidth(rightOffset - leftOffset); } else { extentsRect.setY(leftOffset); extentsRect.setHeight(rightOffset - leftOffset); } extentsRect = localToAbsoluteQuad(FloatRect(extentsRect)).enclosingBoundingBox(); if (!box->isHorizontal()) extentsRect = extentsRect.transposedRect(); bool isFirstOnLine = !box->previousOnLineExists(); bool isLastOnLine = !box->nextOnLineExists(); if (containingBlock->isRubyBase() || containingBlock->isRubyText()) isLastOnLine = !containingBlock->containingBlock()->inlineBoxWrapper()->nextOnLineExists(); bool isFixed = false; IntRect absRect = localToAbsoluteQuad(FloatRect(rect), UseTransforms, &isFixed).enclosingBoundingBox(); bool boxIsHorizontal = !box->isSVGInlineTextBox() ? box->isHorizontal() : !style().svgStyle().isVerticalWritingMode(); // If the containing block is an inline element, we want to check the inlineBoxWrapper orientation // to determine the orientation of the block. In this case we also use the inlineBoxWrapper to // determine if the element is the last on the line. if (containingBlock->inlineBoxWrapper()) { if (containingBlock->inlineBoxWrapper()->isHorizontal() != boxIsHorizontal) { boxIsHorizontal = containingBlock->inlineBoxWrapper()->isHorizontal(); isLastOnLine = !containingBlock->inlineBoxWrapper()->nextOnLineExists(); } } rects.append(SelectionRect(absRect, box->direction(), extentsRect.x(), extentsRect.maxX(), extentsRect.maxY(), 0, box->isLineBreak(), isFirstOnLine, isLastOnLine, false, false, boxIsHorizontal, isFixed, containingBlock->isRubyText(), view().pageNumberForBlockProgressionOffset(absRect.x()))); }
RoundedInnerRectClipper::RoundedInnerRectClipper(LayoutObject& layoutObject, const PaintInfo& paintInfo, const LayoutRect& rect, const FloatRoundedRect& clipRect, RoundedInnerRectClipperBehavior behavior) : m_layoutObject(layoutObject) , m_paintInfo(paintInfo) , m_useDisplayItemList(RuntimeEnabledFeatures::slimmingPaintEnabled() && behavior == ApplyToDisplayListIfEnabled) , m_clipType(m_useDisplayItemList ? m_paintInfo.displayItemTypeForClipping() : DisplayItem::ClipBoxPaintPhaseFirst) { Vector<FloatRoundedRect> roundedRectClips; if (clipRect.isRenderable()) { roundedRectClips.append(clipRect); } else { // We create a rounded rect for each of the corners and clip it, while making sure we clip opposing corners together. if (!clipRect.radii().topLeft().isEmpty() || !clipRect.radii().bottomRight().isEmpty()) { FloatRect topCorner(clipRect.rect().x(), clipRect.rect().y(), rect.maxX() - clipRect.rect().x(), rect.maxY() - clipRect.rect().y()); FloatRoundedRect::Radii topCornerRadii; topCornerRadii.setTopLeft(clipRect.radii().topLeft()); roundedRectClips.append(FloatRoundedRect(topCorner, topCornerRadii)); FloatRect bottomCorner(rect.x().toFloat(), rect.y().toFloat(), clipRect.rect().maxX() - rect.x().toFloat(), clipRect.rect().maxY() - rect.y().toFloat()); FloatRoundedRect::Radii bottomCornerRadii; bottomCornerRadii.setBottomRight(clipRect.radii().bottomRight()); roundedRectClips.append(FloatRoundedRect(bottomCorner, bottomCornerRadii)); } if (!clipRect.radii().topRight().isEmpty() || !clipRect.radii().bottomLeft().isEmpty()) { FloatRect topCorner(rect.x().toFloat(), clipRect.rect().y(), clipRect.rect().maxX() - rect.x().toFloat(), rect.maxY() - clipRect.rect().y()); FloatRoundedRect::Radii topCornerRadii; topCornerRadii.setTopRight(clipRect.radii().topRight()); roundedRectClips.append(FloatRoundedRect(topCorner, topCornerRadii)); FloatRect bottomCorner(clipRect.rect().x(), rect.y().toFloat(), rect.maxX() - clipRect.rect().x(), clipRect.rect().maxY() - rect.y().toFloat()); FloatRoundedRect::Radii bottomCornerRadii; bottomCornerRadii.setBottomLeft(clipRect.radii().bottomLeft()); roundedRectClips.append(FloatRoundedRect(bottomCorner, bottomCornerRadii)); } } if (m_useDisplayItemList) { ASSERT(m_paintInfo.context->displayItemList()); if (m_paintInfo.context->displayItemList()->displayItemConstructionIsDisabled()) return; m_paintInfo.context->displayItemList()->createAndAppend<ClipDisplayItem>(layoutObject, m_clipType, LayoutRect::infiniteIntRect(), roundedRectClips); } else { ClipDisplayItem clipDisplayItem(layoutObject, m_clipType, LayoutRect::infiniteIntRect(), roundedRectClips); clipDisplayItem.replay(*paintInfo.context); } }
LayoutRect RenderNamedFlowThread::decorationsClipRectForBoxInNamedFlowFragment(const RenderBox& box, RenderNamedFlowFragment& fragment) const { LayoutRect visualOverflowRect = fragment.visualOverflowRectForBox(&box); LayoutUnit initialLogicalX = style().isHorizontalWritingMode() ? visualOverflowRect.x() : visualOverflowRect.y(); // The visual overflow rect returned by visualOverflowRectForBox is already flipped but the // RenderRegion::rectFlowPortionForBox method expects it unflipped. flipForWritingModeLocalCoordinates(visualOverflowRect); visualOverflowRect = fragment.rectFlowPortionForBox(&box, visualOverflowRect); // Now flip it again. flipForWritingModeLocalCoordinates(visualOverflowRect); // Take the scrolled offset of this object's parents into consideration. IntSize scrolledContentOffset; RenderBlock* containingBlock = box.containingBlock(); while (containingBlock) { if (containingBlock->isRenderNamedFlowThread()) { // We've reached the flow thread, take the scrolled offset of the region into consideration. ASSERT(containingBlock == this); scrolledContentOffset += fragment.fragmentContainer().scrolledContentOffset(); break; } scrolledContentOffset += containingBlock->scrolledContentOffset(); containingBlock = containingBlock->containingBlock(); } if (!scrolledContentOffset.isZero()) { if (style().isFlippedBlocksWritingMode()) scrolledContentOffset = -scrolledContentOffset; visualOverflowRect.inflateX(scrolledContentOffset.width()); visualOverflowRect.inflateY(scrolledContentOffset.height()); } // Layers are in physical coordinates so the origin must be moved to the physical top-left of the flowthread. if (style().isFlippedBlocksWritingMode()) { if (style().isHorizontalWritingMode()) visualOverflowRect.moveBy(LayoutPoint(0, height())); else visualOverflowRect.moveBy(LayoutPoint(width(), 0)); } const RenderBox* iterBox = &box; while (iterBox && iterBox != this) { RenderBlock* containerBlock = iterBox->containingBlock(); // FIXME: This doesn't work properly with flipped writing modes. // https://bugs.webkit.org/show_bug.cgi?id=125149 if (iterBox->isPositioned()) { // For positioned elements, just use the layer's absolute bounding box. visualOverflowRect.moveBy(iterBox->layer()->absoluteBoundingBox().location()); break; } LayoutRect currentBoxRect = iterBox->frameRect(); if (iterBox->style().isFlippedBlocksWritingMode()) { if (iterBox->style().isHorizontalWritingMode()) currentBoxRect.setY(currentBoxRect.height() - currentBoxRect.maxY()); else currentBoxRect.setX(currentBoxRect.width() - currentBoxRect.maxX()); } if (containerBlock->style().writingMode() != iterBox->style().writingMode()) iterBox->flipForWritingMode(currentBoxRect); visualOverflowRect.moveBy(currentBoxRect.location()); iterBox = containerBlock; } // Since the purpose of this method is to make sure the borders of a fragmented // element don't overflow the region in the fragmentation direction, there's no // point in restricting the clipping rect on the logical X axis. // This also saves us the trouble of handling percent-based widths and margins // since the absolute bounding box of a positioned element would not contain // the correct coordinates relative to the region we're interested in, but rather // relative to the actual flow thread. if (style().isHorizontalWritingMode()) { if (initialLogicalX < visualOverflowRect.x()) visualOverflowRect.shiftXEdgeTo(initialLogicalX); if (visualOverflowRect.width() < frameRect().width()) visualOverflowRect.setWidth(frameRect().width()); } else { if (initialLogicalX < visualOverflowRect.y()) visualOverflowRect.shiftYEdgeTo(initialLogicalX); if (visualOverflowRect.height() < frameRect().height()) visualOverflowRect.setHeight(frameRect().height()); } return visualOverflowRect; }
void TableCellPainter::paintCollapsedBorders(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { ASSERT(paintInfo.phase == PaintPhaseCollapsedTableBorders); if (!paintInfo.shouldPaintWithinRoot(&m_renderTableCell) || m_renderTableCell.style()->visibility() != VISIBLE) return; LayoutRect paintRect = paintBounds(paintOffset, AddOffsetFromParent); if (paintRect.y() - m_renderTableCell.table()->outerBorderTop() >= paintInfo.rect.maxY()) return; if (paintRect.maxY() + m_renderTableCell.table()->outerBorderBottom() <= paintInfo.rect.y()) return; if (!m_renderTableCell.table()->currentBorderValue()) return; RenderDrawingRecorder recorder(paintInfo.context, m_renderTableCell, paintInfo.phase, paintRect); if (recorder.canUseCachedDrawing()) return; const RenderStyle* styleForCellFlow = m_renderTableCell.styleForCellFlow(); CollapsedBorderValue leftVal = cachedCollapsedLeftBorder(styleForCellFlow); CollapsedBorderValue rightVal = cachedCollapsedRightBorder(styleForCellFlow); CollapsedBorderValue topVal = cachedCollapsedTopBorder(styleForCellFlow); CollapsedBorderValue bottomVal = cachedCollapsedBottomBorder(styleForCellFlow); // Adjust our x/y/width/height so that we paint the collapsed borders at the correct location. int topWidth = topVal.width(); int bottomWidth = bottomVal.width(); int leftWidth = leftVal.width(); int rightWidth = rightVal.width(); IntRect borderRect = pixelSnappedIntRect(paintRect.x() - leftWidth / 2, paintRect.y() - topWidth / 2, paintRect.width() + leftWidth / 2 + (rightWidth + 1) / 2, paintRect.height() + topWidth / 2 + (bottomWidth + 1) / 2); EBorderStyle topStyle = collapsedBorderStyle(topVal.style()); EBorderStyle bottomStyle = collapsedBorderStyle(bottomVal.style()); EBorderStyle leftStyle = collapsedBorderStyle(leftVal.style()); EBorderStyle rightStyle = collapsedBorderStyle(rightVal.style()); bool renderTop = topStyle > BHIDDEN && !topVal.isTransparent(); bool renderBottom = bottomStyle > BHIDDEN && !bottomVal.isTransparent(); bool renderLeft = leftStyle > BHIDDEN && !leftVal.isTransparent(); bool renderRight = rightStyle > BHIDDEN && !rightVal.isTransparent(); // We never paint diagonals at the joins. We simply let the border with the highest // precedence paint on top of borders with lower precedence. CollapsedBorders borders; borders.addBorder(topVal, BSTop, renderTop, borderRect.x(), borderRect.y(), borderRect.maxX(), borderRect.y() + topWidth, topStyle); borders.addBorder(bottomVal, BSBottom, renderBottom, borderRect.x(), borderRect.maxY() - bottomWidth, borderRect.maxX(), borderRect.maxY(), bottomStyle); borders.addBorder(leftVal, BSLeft, renderLeft, borderRect.x(), borderRect.y(), borderRect.x() + leftWidth, borderRect.maxY(), leftStyle); borders.addBorder(rightVal, BSRight, renderRight, borderRect.maxX() - rightWidth, borderRect.y(), borderRect.maxX(), borderRect.maxY(), rightStyle); GraphicsContext* graphicsContext = paintInfo.context; bool antialias = BoxPainter::shouldAntialiasLines(graphicsContext); for (CollapsedBorder* border = borders.nextBorder(); border; border = borders.nextBorder()) { if (border->borderValue.isSameIgnoringColor(*m_renderTableCell.table()->currentBorderValue())) { ObjectPainter::drawLineForBoxSide(graphicsContext, border->x1, border->y1, border->x2, border->y2, border->side, border->borderValue.color().resolve(m_renderTableCell.resolveColor(CSSPropertyColor)), border->style, 0, 0, antialias); } } }
// Return true if rect |a| is below |b|. False otherwise. // For overlapping rects, |a| is considered to be below |b| // if both edges of |a| are below the respective ones of |b| static inline bool below(const LayoutRect& a, const LayoutRect& b) { return a.y() >= b.maxY() || (a.y() >= b.y() && a.maxY() > b.maxY()); }
bool LayoutRect::intersects(const LayoutRect& other) const { // Checking emptiness handles negative widths as well as zero. return !isEmpty() && !other.isEmpty() && x() < other.maxX() && other.x() < maxX() && y() < other.maxY() && other.y() < maxY(); }
// Return true if rect |a| is below |b|. False otherwise. static inline bool below(const LayoutRect& a, const LayoutRect& b) { return a.y() > b.maxY(); }
static inline LayoutUnit end(FocusDirection direction, const LayoutRect& rect) { return isHorizontalMove(direction) ? rect.maxY() : rect.maxX(); }
void GridPainter::paintChildren(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { DCHECK(!m_layoutGrid.needsLayout()); LayoutRect localVisualRect = LayoutRect(paintInfo.cullRect().m_rect); localVisualRect.moveBy(-paintOffset); Vector<LayoutUnit> columnPositions = m_layoutGrid.columnPositions(); if (!m_layoutGrid.styleRef().isLeftToRightDirection()) { // Translate columnPositions in RTL as we need the physical coordinates of // the columns in order to call dirtiedGridAreas(). for (size_t i = 0; i < columnPositions.size(); i++) columnPositions[i] = m_layoutGrid.translateRTLCoordinate(columnPositions[i]); // We change the order of tracks in columnPositions, as in RTL the leftmost // track will be the last one. std::sort(columnPositions.begin(), columnPositions.end()); } GridSpan dirtiedColumns = dirtiedGridAreas( columnPositions, localVisualRect.x(), localVisualRect.maxX()); GridSpan dirtiedRows = dirtiedGridAreas( m_layoutGrid.rowPositions(), localVisualRect.y(), localVisualRect.maxY()); if (!m_layoutGrid.styleRef().isLeftToRightDirection()) { // As we changed the order of tracks previously, we need to swap the dirtied // columns in RTL. size_t lastLine = columnPositions.size() - 1; dirtiedColumns = GridSpan::translatedDefiniteGridSpan( lastLine - dirtiedColumns.endLine(), lastLine - dirtiedColumns.startLine()); } Vector<std::pair<LayoutBox*, size_t>> gridItemsToBePainted; for (const auto& row : dirtiedRows) { for (const auto& column : dirtiedColumns) { const Vector<LayoutBox*, 1>& children = m_layoutGrid.gridCell(row, column); for (auto* child : children) gridItemsToBePainted.append( std::make_pair(child, m_layoutGrid.paintIndexForGridItem(child))); } } for (auto* item : m_layoutGrid.itemsOverflowingGridArea()) { if (item->frameRect().intersects(localVisualRect)) gridItemsToBePainted.append( std::make_pair(item, m_layoutGrid.paintIndexForGridItem(item))); } std::stable_sort(gridItemsToBePainted.begin(), gridItemsToBePainted.end(), compareOrderModifiedDocumentOrder); LayoutBox* previous = 0; for (const auto& gridItemAndPaintIndex : gridItemsToBePainted) { // We might have duplicates because of spanning children are included in all // cells they span. Skip them here to avoid painting items several times. LayoutBox* current = gridItemAndPaintIndex.first; if (current == previous) continue; BlockPainter(m_layoutGrid) .paintAllChildPhasesAtomically(*current, paintInfo, paintOffset); previous = current; } }
LayoutRect ScrollAlignment::getRectToExpose(const LayoutRect& visibleRect, const LayoutRect& exposeRect, const ScrollAlignment& alignX, const ScrollAlignment& alignY) { // Determine the appropriate X behavior. ScrollAlignmentBehavior scrollX; LayoutRect exposeRectX(exposeRect.x(), visibleRect.y(), exposeRect.width(), visibleRect.height()); LayoutUnit intersectWidth = intersection(visibleRect, exposeRectX).width(); if (intersectWidth == exposeRect.width() || intersectWidth >= MIN_INTERSECT_FOR_REVEAL) { // If the rectangle is fully visible, use the specified visible behavior. // If the rectangle is partially visible, but over a certain threshold, // then treat it as fully visible to avoid unnecessary horizontal scrolling scrollX = getVisibleBehavior(alignX); } else if (intersectWidth == visibleRect.width()) { // If the rect is bigger than the visible area, don't bother trying to center. Other alignments will work. scrollX = getVisibleBehavior(alignX); if (scrollX == ScrollAlignmentCenter) scrollX = ScrollAlignmentNoScroll; } else if (intersectWidth > 0) { // If the rectangle is partially visible, but not above the minimum threshold, use the specified partial behavior scrollX = getPartialBehavior(alignX); } else { scrollX = getHiddenBehavior(alignX); } if (scrollX == ScrollAlignmentClosestEdge) { // Closest edge is the right in two cases: // (1) exposeRect to the right of and smaller than visibleRect // (2) exposeRect to the left of and larger than visibleRect if ((exposeRect.maxX() > visibleRect.maxX() && exposeRect.width() < visibleRect.width()) || (exposeRect.maxX() < visibleRect.maxX() && exposeRect.width() > visibleRect.width())) { scrollX = ScrollAlignmentRight; } } // Given the X behavior, compute the X coordinate. LayoutUnit x; if (scrollX == ScrollAlignmentNoScroll) x = visibleRect.x(); else if (scrollX == ScrollAlignmentRight) x = exposeRect.maxX() - visibleRect.width(); else if (scrollX == ScrollAlignmentCenter) x = exposeRect.x() + (exposeRect.width() - visibleRect.width()) / 2; else x = exposeRect.x(); // Determine the appropriate Y behavior. ScrollAlignmentBehavior scrollY; LayoutRect exposeRectY(visibleRect.x(), exposeRect.y(), visibleRect.width(), exposeRect.height()); LayoutUnit intersectHeight = intersection(visibleRect, exposeRectY).height(); if (intersectHeight == exposeRect.height()) { // If the rectangle is fully visible, use the specified visible behavior. scrollY = getVisibleBehavior(alignY); } else if (intersectHeight == visibleRect.height()) { // If the rect is bigger than the visible area, don't bother trying to center. Other alignments will work. scrollY = getVisibleBehavior(alignY); if (scrollY == ScrollAlignmentCenter) scrollY = ScrollAlignmentNoScroll; } else if (intersectHeight > 0) { // If the rectangle is partially visible, use the specified partial behavior scrollY = getPartialBehavior(alignY); } else { scrollY = getHiddenBehavior(alignY); } if (scrollY == ScrollAlignmentClosestEdge) { // Closest edge is the bottom in two cases: // (1) exposeRect below and smaller than visibleRect // (2) exposeRect above and larger than visibleRect if ((exposeRect.maxY() > visibleRect.maxY() && exposeRect.height() < visibleRect.height()) || (exposeRect.maxY() < visibleRect.maxY() && exposeRect.height() > visibleRect.height())) { scrollY = ScrollAlignmentBottom; } } // Given the Y behavior, compute the Y coordinate. LayoutUnit y; if (scrollY == ScrollAlignmentNoScroll) y = visibleRect.y(); else if (scrollY == ScrollAlignmentBottom) y = exposeRect.maxY() - visibleRect.height(); else if (scrollY == ScrollAlignmentCenter) y = exposeRect.y() + (exposeRect.height() - visibleRect.height()) / 2; else y = exposeRect.y(); return LayoutRect(LayoutPoint(x, y), visibleRect.size()); }
bool LayoutRect::contains(const LayoutRect& other) const { return x() <= other.x() && maxX() >= other.maxX() && y() <= other.y() && maxY() >= other.maxY(); }
LayoutUnit RenderRegion::logicalBottomOfFlowThreadContentRect(const LayoutRect& rect) const { ASSERT(isValid()); return flowThread()->isHorizontalWritingMode() ? rect.maxY() : rect.maxX(); }
// This method calculates the exitPoint from the startingRect and the entryPoint into the candidate rect. // The line between those 2 points is the closest distance between the 2 rects. // Takes care of overlapping rects, defining points so that the distance between them // is zero where necessary void entryAndExitPointsForDirection(FocusType type, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint) { switch (type) { case FocusTypeLeft: exitPoint.setX(startingRect.x()); if (potentialRect.maxX() < startingRect.x()) entryPoint.setX(potentialRect.maxX()); else entryPoint.setX(startingRect.x()); break; case FocusTypeUp: exitPoint.setY(startingRect.y()); if (potentialRect.maxY() < startingRect.y()) entryPoint.setY(potentialRect.maxY()); else entryPoint.setY(startingRect.y()); break; case FocusTypeRight: exitPoint.setX(startingRect.maxX()); if (potentialRect.x() > startingRect.maxX()) entryPoint.setX(potentialRect.x()); else entryPoint.setX(startingRect.maxX()); break; case FocusTypeDown: exitPoint.setY(startingRect.maxY()); if (potentialRect.y() > startingRect.maxY()) entryPoint.setY(potentialRect.y()); else entryPoint.setY(startingRect.maxY()); break; default: ASSERT_NOT_REACHED(); } switch (type) { case FocusTypeLeft: case FocusTypeRight: if (below(startingRect, potentialRect)) { exitPoint.setY(startingRect.y()); if (potentialRect.maxY() < startingRect.y()) entryPoint.setY(potentialRect.maxY()); else entryPoint.setY(startingRect.y()); } else if (below(potentialRect, startingRect)) { exitPoint.setY(startingRect.maxY()); if (potentialRect.y() > startingRect.maxY()) entryPoint.setY(potentialRect.y()); else entryPoint.setY(startingRect.maxY()); } else { exitPoint.setY(max(startingRect.y(), potentialRect.y())); entryPoint.setY(exitPoint.y()); } break; case FocusTypeUp: case FocusTypeDown: if (rightOf(startingRect, potentialRect)) { exitPoint.setX(startingRect.x()); if (potentialRect.maxX() < startingRect.x()) entryPoint.setX(potentialRect.maxX()); else entryPoint.setX(startingRect.x()); } else if (rightOf(potentialRect, startingRect)) { exitPoint.setX(startingRect.maxX()); if (potentialRect.x() > startingRect.maxX()) entryPoint.setX(potentialRect.x()); else entryPoint.setX(startingRect.maxX()); } else { exitPoint.setX(max(startingRect.x(), potentialRect.x())); entryPoint.setX(exitPoint.x()); } break; default: ASSERT_NOT_REACHED(); } }
static inline LayoutUnit end(FocusType type, const LayoutRect& rect) { return isHorizontalMove(type) ? rect.maxY() : rect.maxX(); }
void ColumnBalancer::traverseSubtree(const LayoutBox& box) { if (box.childrenInline() && box.isLayoutBlockFlow()) { // Look for breaks between lines. for (const RootInlineBox* line = toLayoutBlockFlow(box).firstRootBox(); line; line = line->nextRootBox()) { LayoutUnit lineTopInFlowThread = m_flowThreadOffset + line->lineTopWithLeading(); if (lineTopInFlowThread < group().logicalTopInFlowThread()) continue; if (lineTopInFlowThread >= group().logicalBottomInFlowThread()) break; examineLine(*line); } } const LayoutFlowThread* flowThread = group().columnSet().flowThread(); bool isHorizontalWritingMode = flowThread->isHorizontalWritingMode(); // Look for breaks between and inside block-level children. Even if this is a block flow with // inline children, there may be interesting floats to examine here. for (const LayoutObject* child = box.slowFirstChild(); child; child = child->nextSibling()) { if (!child->isBox() || child->isInline()) continue; const LayoutBox& childBox = toLayoutBox(*child); LayoutRect overflowRect = childBox.layoutOverflowRect(); LayoutUnit childLogicalBottomWithOverflow = childBox.logicalTop() + (isHorizontalWritingMode ? overflowRect.maxY() : overflowRect.maxX()); if (m_flowThreadOffset + childLogicalBottomWithOverflow <= group().logicalTopInFlowThread()) { // This child is fully above the fragmentainer group we're examining. continue; } LayoutUnit childLogicalTopWithOverflow = childBox.logicalTop() + (isHorizontalWritingMode ? overflowRect.y() : overflowRect.x()); if (m_flowThreadOffset + childLogicalTopWithOverflow >= group().logicalBottomInFlowThread()) { // This child is fully below the fragmentainer group we're examining. We cannot just // stop here, though, thanks to negative margins. So keep looking. continue; } if (childBox.isOutOfFlowPositioned() || childBox.isColumnSpanAll()) continue; // Tables are wicked. Both table rows and table cells are relative to their table section. LayoutUnit offsetForThisChild = childBox.isTableRow() ? LayoutUnit() : childBox.logicalTop(); m_flowThreadOffset += offsetForThisChild; examineBoxAfterEntering(childBox); // Unless the child is unsplittable, or if the child establishes an inner multicol // container, we descend into its subtree for further examination. if (childBox.paginationBreakability() != LayoutBox::ForbidBreaks && (!childBox.isLayoutBlockFlow() || !toLayoutBlockFlow(childBox).multiColumnFlowThread())) traverseSubtree(childBox); examineBoxBeforeLeaving(childBox); m_flowThreadOffset -= offsetForThisChild; } }