TEST_F(LayoutSVGRootTest, VisualRectMappingWithViewportClipWithoutBorder) { setBodyInnerHTML( "<svg id='root' style='width: 200px; height: 100px; overflow: hidden' " "viewBox='0 0 200 100'>" " <rect id='rect' x='80' y='80' width='100' height='100'/>" "</svg>"); const LayoutSVGRoot& root = *toLayoutSVGRoot(getLayoutObjectByElementId("root")); const LayoutSVGShape& svgRect = *toLayoutSVGShape(getLayoutObjectByElementId("rect")); LayoutRect rect = SVGLayoutSupport::visualRectInAncestorSpace(svgRect, root); // (80, 80, 100, 100) clipped by (0, 0, 200, 100). EXPECT_EQ(LayoutRect(80, 80, 100, 20), rect); LayoutRect rootVisualRect = static_cast<const LayoutObject&>(root).localVisualRect(); // SVG root doesn't have box decoration background, so just use clipped // overflow of children. EXPECT_EQ(LayoutRect(80, 80, 100, 20), rootVisualRect); rect = rootVisualRect; EXPECT_TRUE(root.mapToVisualRectInAncestorSpace(&root, rect)); EXPECT_EQ(LayoutRect(80, 80, 100, 20), rect); }
TEST_F(LayoutSVGRootTest, VisualRectMappingWithViewportClipAndBorder) { setBodyInnerHTML( "<svg id='root' style='border: 10px solid red; width: 200px; height: " "100px; overflow: hidden' viewBox='0 0 200 100'>" " <rect id='rect' x='80' y='80' width='100' height='100'/>" "</svg>"); const LayoutSVGRoot& root = *toLayoutSVGRoot(getLayoutObjectByElementId("root")); const LayoutSVGShape& svgRect = *toLayoutSVGShape(getLayoutObjectByElementId("rect")); LayoutRect rect = SVGLayoutSupport::visualRectInAncestorSpace(svgRect, root); // (80, 80, 100, 100) added by root's content rect offset from border rect, // clipped by (10, 10, 200, 100). EXPECT_EQ(LayoutRect(90, 90, 100, 20), rect); LayoutRect rootVisualRect = static_cast<const LayoutObject&>(root).localVisualRect(); // SVG root with overflow:hidden doesn't include overflow from children, just // border box rect. EXPECT_EQ(LayoutRect(0, 0, 220, 120), rootVisualRect); rect = rootVisualRect; EXPECT_TRUE(root.mapToVisualRectInAncestorSpace(&root, rect)); // LayoutSVGRoot should not apply overflow clip on its own rect. EXPECT_EQ(LayoutRect(0, 0, 220, 120), rect); }
TEST_F(LayoutSVGRootTest, VisualRectMappingWithoutViewportClipWithBorder) { setBodyInnerHTML( "<svg id='root' style='border: 10px solid red; width: 200px; height: " "100px; overflow: visible' viewBox='0 0 200 100'>" " <rect id='rect' x='80' y='80' width='100' height='100'/>" "</svg>"); const LayoutSVGRoot& root = *toLayoutSVGRoot(getLayoutObjectByElementId("root")); const LayoutSVGShape& svgRect = *toLayoutSVGShape(getLayoutObjectByElementId("rect")); LayoutRect rect = SVGLayoutSupport::visualRectInAncestorSpace(svgRect, root); // (80, 80, 100, 100) added by root's content rect offset from border rect, // not clipped. EXPECT_EQ(LayoutRect(90, 90, 100, 100), rect); LayoutRect rootVisualRect = static_cast<const LayoutObject&>(root).localVisualRect(); // SVG root's overflow includes overflow from descendants. EXPECT_EQ(LayoutRect(0, 0, 220, 190), rootVisualRect); rect = rootVisualRect; EXPECT_TRUE(root.mapToVisualRectInAncestorSpace(&root, rect)); EXPECT_EQ(LayoutRect(0, 0, 220, 190), rect); }
void SVGLayoutSupport::layoutChildren( LayoutObject* firstChild, bool forceLayout, bool screenScalingFactorChanged, bool layoutSizeChanged) { for (LayoutObject* child = firstChild; child; child = child->nextSibling()) { bool forceChildLayout = forceLayout; if (screenScalingFactorChanged) { // If the screen scaling factor changed we need to update the text // metrics (note: this also happens for layoutSizeChanged=true). if (child->isSVGText()) toLayoutSVGText(child)->setNeedsTextMetricsUpdate(); forceChildLayout = true; } if (layoutSizeChanged) { // When selfNeedsLayout is false and the layout size changed, we have to check whether this child uses relative lengths if (SVGElement* element = child->node()->isSVGElement() ? toSVGElement(child->node()) : 0) { if (element->hasRelativeLengths()) { // FIXME: this should be done on invalidation, not during layout. // When the layout size changed and when using relative values tell the LayoutSVGShape to update its shape object if (child->isSVGShape()) { toLayoutSVGShape(child)->setNeedsShapeUpdate(); } else if (child->isSVGText()) { toLayoutSVGText(child)->setNeedsTextMetricsUpdate(); toLayoutSVGText(child)->setNeedsPositioningValuesUpdate(); } forceChildLayout = true; } } } // Resource containers are nasty: they can invalidate clients outside the current SubtreeLayoutScope. // Since they only care about viewport size changes (to resolve their relative lengths), we trigger // their invalidation directly from SVGSVGElement::svgAttributeChange() or at a higher // SubtreeLayoutScope (in LayoutView::layout()). We do not create a SubtreeLayoutScope for // resources because their ability to reference each other leads to circular layout. We protect // against that within the layout code for resources, but it causes assertions if we use a // SubTreeLayoutScope for them. if (child->isSVGResourceContainer()) { // Lay out any referenced resources before the child. layoutResourcesIfNeeded(child); child->layoutIfNeeded(); } else { SubtreeLayoutScope layoutScope(*child); if (forceChildLayout) layoutScope.setNeedsLayout(child, LayoutInvalidationReason::SvgChanged); // Lay out any referenced resources before the child. layoutResourcesIfNeeded(child); child->layoutIfNeeded(); } } }
bool SVGGeometryElement::isPointInStroke(PassRefPtrWillBeRawPtr<SVGPointTearOff> point) const { document().updateLayoutIgnorePendingStylesheets(); // FIXME: Eventually we should support isPointInStroke for display:none elements. if (!layoutObject() || !layoutObject()->isSVGShape()) return false; HitTestRequest request(HitTestRequest::ReadOnly); PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_GEOMETRY_HITTESTING, request, layoutObject()->style()->pointerEvents()); hitRules.canHitFill = false; return toLayoutSVGShape(layoutObject())->nodeAtFloatPointInternal(request, point->target()->value(), hitRules); }
void SVGLayoutSupport::layoutChildren(LayoutObject* start, bool selfNeedsLayout) { // When hasRelativeLengths() is false, no descendants have relative lengths // (hence no one is interested in viewport size changes). bool layoutSizeChanged = toSVGElement(start->node())->hasRelativeLengths() && layoutSizeOfNearestViewportChanged(start); bool transformChanged = transformToRootChanged(start); for (LayoutObject* child = start->slowFirstChild(); child; child = child->nextSibling()) { bool forceLayout = selfNeedsLayout; if (transformChanged) { // If the transform changed we need to update the text metrics (note: this also happens for layoutSizeChanged=true). if (child->isSVGText()) toLayoutSVGText(child)->setNeedsTextMetricsUpdate(); forceLayout = true; } if (layoutSizeChanged) { // When selfNeedsLayout is false and the layout size changed, we have to check whether this child uses relative lengths if (SVGElement* element = child->node()->isSVGElement() ? toSVGElement(child->node()) : 0) { if (element->hasRelativeLengths()) { // FIXME: this should be done on invalidation, not during layout. // When the layout size changed and when using relative values tell the LayoutSVGShape to update its shape object if (child->isSVGShape()) { toLayoutSVGShape(child)->setNeedsShapeUpdate(); } else if (child->isSVGText()) { toLayoutSVGText(child)->setNeedsTextMetricsUpdate(); toLayoutSVGText(child)->setNeedsPositioningValuesUpdate(); } forceLayout = true; } } } SubtreeLayoutScope layoutScope(*child); // Resource containers are nasty: they can invalidate clients outside the current SubtreeLayoutScope. // Since they only care about viewport size changes (to resolve their relative lengths), we trigger // their invalidation directly from SVGSVGElement::svgAttributeChange() or at a higher // SubtreeLayoutScope (in LayoutView::layout()). if (forceLayout && !child->isSVGResourceContainer()) layoutScope.setNeedsLayout(child, LayoutInvalidationReason::SvgChanged); // Lay out any referenced resources before the child. layoutResourcesIfNeeded(child); child->layoutIfNeeded(); } }
void SVGLineElement::svgAttributeChanged(const QualifiedName& attrName) { if (attrName == SVGNames::x1Attr || attrName == SVGNames::y1Attr || attrName == SVGNames::x2Attr || attrName == SVGNames::y2Attr) { updateRelativeLengthsInformation(); LayoutSVGShape* layoutObject = toLayoutSVGShape(this->layoutObject()); if (!layoutObject) return; SVGElement::InvalidationGuard invalidationGuard(this); layoutObject->setNeedsShapeUpdate(); markForLayoutAndParentResourceInvalidation(layoutObject); return; } SVGGeometryElement::svgAttributeChanged(attrName); }
void SVGCircleElement::svgAttributeChanged(const QualifiedName& attrName) { if (attrName == SVGNames::rAttr || attrName == SVGNames::cxAttr || attrName == SVGNames::cyAttr) { SVGElement::InvalidationGuard invalidationGuard(this); invalidateSVGPresentationAttributeStyle(); setNeedsStyleRecalc(LocalStyleChange, StyleChangeReasonForTracing::fromAttribute(attrName)); updateRelativeLengthsInformation(); LayoutSVGShape* layoutObject = toLayoutSVGShape(this->layoutObject()); if (!layoutObject) return; layoutObject->setNeedsShapeUpdate(); markForLayoutAndParentResourceInvalidation(layoutObject); return; } SVGGraphicsElement::svgAttributeChanged(attrName); }
void SVGLayoutSupport::computeContainerBoundingBoxes( const LayoutObject* container, FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, FloatRect& strokeBoundingBox, FloatRect& paintInvalidationBoundingBox) { objectBoundingBox = FloatRect(); objectBoundingBoxValid = false; strokeBoundingBox = FloatRect(); // When computing the strokeBoundingBox, we use the paintInvalidationRects of // the container's children so that the container's stroke includes the // resources applied to the children (such as clips and filters). This allows // filters applied to containers to correctly bound the children, and also // improves inlining of SVG content, as the stroke bound is used in that // situation also. for (LayoutObject* current = container->slowFirstChild(); current; current = current->nextSibling()) { if (current->isSVGHiddenContainer()) continue; // Don't include elements in the union that do not layout. if (current->isSVGShape() && toLayoutSVGShape(current)->isShapeEmpty()) continue; if (current->isSVGText() && !toLayoutSVGText(current)->isObjectBoundingBoxValid()) continue; const AffineTransform& transform = current->localToSVGParentTransform(); updateObjectBoundingBox(objectBoundingBox, objectBoundingBoxValid, current, transform.mapRect(current->objectBoundingBox())); strokeBoundingBox.unite(transform.mapRect( current->paintInvalidationRectInLocalSVGCoordinates())); } paintInvalidationBoundingBox = strokeBoundingBox; }
LayoutRect SVGLayoutSupport::transformPaintInvalidationRect( const LayoutObject& object, const AffineTransform& rootTransform, const FloatRect& localRect) { FloatRect adjustedRect = rootTransform.mapRect(localRect); if (object.isSVGShape() && object.styleRef().svgStyle().hasStroke()) { if (float strokeWidthForHairlinePadding = toLayoutSVGShape(object).strokeWidth()) { // For hairline strokes (stroke-width < 1 in device space), Skia // rasterizes up to 0.4(9) off the stroke center. That means // enclosingIntRect is not enough - we must also pad to 0.5. // This is still fragile as it misses out on CC/DSF CTM components. const FloatSize strokeSize = rootTransform.mapSize(FloatSize( strokeWidthForHairlinePadding, strokeWidthForHairlinePadding)); if (strokeSize.width() < 1 || strokeSize.height() < 1) { float pad = 0.5f - std::min(strokeSize.width(), strokeSize.height()) / 2; DCHECK_GT(pad, 0); // Additionally, square/round caps can potentially introduce an outset // <= 0.5 if (object.styleRef().svgStyle().capStyle() != ButtCap) pad += 0.5f; adjustedRect.inflate(pad); } } } if (adjustedRect.isEmpty()) return LayoutRect(); // Use enclosingIntRect because we cannot properly apply subpixel offset of // the SVGRoot since we don't know the desired subpixel accumulation at this // point. return LayoutRect(enclosingIntRect(adjustedRect)); }
LayoutRect SVGLayoutSupport::transformPaintInvalidationRect(const LayoutObject& object, const AffineTransform& rootTransform, const FloatRect& localRect) { FloatRect adjustedRect = rootTransform.mapRect(localRect); if (object.isSVGShape() && object.styleRef().svgStyle().hasStroke()) { if (float strokeWidthForHairlinePadding = toLayoutSVGShape(object).strokeWidth()) { // For hairline strokes (stroke-width < 1 in device space), Skia rasterizes up to 0.4(9) off // the stroke center. That means enclosingIntRect is not enough - we must also pad to 0.5. // This is still fragile as it misses out on CC/DSF CTM components. const FloatSize strokeSize = rootTransform.mapSize( FloatSize(strokeWidthForHairlinePadding, strokeWidthForHairlinePadding)); if (strokeSize.width() < 1 || strokeSize.height() < 1) { const float pad = 0.5f - std::min(strokeSize.width(), strokeSize.height()) / 2; ASSERT(pad > 0); adjustedRect.inflate(pad); } } } if (adjustedRect.isEmpty()) return LayoutRect(); return LayoutRect(enclosingIntRect(adjustedRect)); }