bool LayoutSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint) { FloatPoint point = nodeAtPoint; if (!SVGLayoutSupport::pointInClippingArea(this, point)) return false; if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { AffineTransform transform; transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); point = transform.inverse().mapPoint(point); } AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->calculateAnimatedLocalTransform(); if (!animatedLocalTransform.isInvertible()) return false; point = animatedLocalTransform.inverse().mapPoint(point); for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { LayoutObject* layoutObject = childElement->layoutObject(); if (!layoutObject) continue; if (!layoutObject->isSVGShape() && !layoutObject->isSVGText() && !isSVGUseElement(*childElement)) continue; IntPoint hitPoint; HitTestResult result(HitTestRequest::SVGClipContent, hitPoint); if (layoutObject->nodeAtFloatPoint(result, point, HitTestForeground)) return true; } return false; }
bool SVGSVGElement::checkIntersectionOrEnclosure( const SVGElement& element, const FloatRect& rect, CheckIntersectionOrEnclosure mode) const { LayoutObject* layoutObject = element.layoutObject(); ASSERT(!layoutObject || layoutObject->style()); if (!layoutObject || layoutObject->style()->pointerEvents() == EPointerEvents::None) return false; if (!isIntersectionOrEnclosureTarget(layoutObject)) return false; AffineTransform ctm = toSVGGraphicsElement(element).computeCTM( AncestorScope, DisallowStyleUpdate, this); FloatRect mappedRepaintRect = ctm.mapRect(layoutObject->visualRectInLocalSVGCoordinates()); bool result = false; switch (mode) { case CheckIntersection: result = intersectsAllowingEmpty(rect, mappedRepaintRect); break; case CheckEnclosure: result = rect.contains(mappedRepaintRect); break; default: ASSERT_NOT_REACHED(); break; } return result; }
void LayoutSVGResourceMasker::calculateMaskContentPaintInvalidationRect() { for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { LayoutObject* layoutObject = childElement->layoutObject(); if (!layoutObject) continue; const ComputedStyle* style = layoutObject->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; m_maskContentBoundaries.unite(layoutObject->localToParentTransform().mapRect(layoutObject->paintInvalidationRectInLocalCoordinates())); } }
sk_sp<const SkPicture> LayoutSVGResourceMasker::createContentPicture( AffineTransform& contentTransformation, const FloatRect& targetBoundingBox, GraphicsContext& context) { SVGUnitTypes::SVGUnitType contentUnits = toSVGMaskElement(element()) ->maskContentUnits() ->currentValue() ->enumValue(); if (contentUnits == SVGUnitTypes::kSvgUnitTypeObjectboundingbox) { contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y()); contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height()); } if (m_maskContentPicture) return m_maskContentPicture; SubtreeContentTransformScope contentTransformScope(contentTransformation); // Using strokeBoundingBox instead of visualRectInLocalCoordinates // to avoid the intersection with local clips/mask, which may yield incorrect // results when mixing objectBoundingBox and userSpaceOnUse units. // http://crbug.com/294900 FloatRect bounds = strokeBoundingBox(); SkPictureBuilder pictureBuilder(bounds, nullptr, &context); ColorFilter maskContentFilter = style()->svgStyle().colorInterpolation() == CI_LINEARRGB ? ColorFilterSRGBToLinearRGB : ColorFilterNone; pictureBuilder.context().setColorFilter(maskContentFilter); for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { LayoutObject* layoutObject = childElement->layoutObject(); if (!layoutObject) continue; const ComputedStyle* style = layoutObject->style(); if (!style || style->display() == EDisplay::None || style->visibility() != EVisibility::Visible) continue; SVGPaintContext::paintSubtree(pictureBuilder.context(), layoutObject); } m_maskContentPicture = pictureBuilder.endRecording(); return m_maskContentPicture; }
void LayoutSVGResourceClipper::calculateClipContentPaintInvalidationRect() { // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip. for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { LayoutObject* layoutObject = childElement->layoutObject(); if (!layoutObject) continue; if (!layoutObject->isSVGShape() && !layoutObject->isSVGText() && !isSVGUseElement(*childElement)) continue; const ComputedStyle* style = layoutObject->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; m_clipBoundaries.unite(layoutObject->localToParentTransform().mapRect(layoutObject->paintInvalidationRectInLocalCoordinates())); } m_clipBoundaries = toSVGClipPathElement(element())->calculateAnimatedLocalTransform().mapRect(m_clipBoundaries); }
void LayoutSVGResourceMasker::calculateMaskContentVisualRect() { for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { LayoutObject* layoutObject = childElement->layoutObject(); if (!layoutObject) continue; const ComputedStyle* style = layoutObject->style(); if (!style || style->display() == EDisplay::None || style->visibility() != EVisibility::Visible) continue; m_maskContentBoundaries.unite( layoutObject->localToSVGParentTransform().mapRect( layoutObject->visualRectInLocalSVGCoordinates())); } }
PassRefPtr<const SkPicture> LayoutSVGResourceMasker::createContentPicture(AffineTransform& contentTransformation, const FloatRect& targetBoundingBox) { SVGUnitTypes::SVGUnitType contentUnits = toSVGMaskElement(element())->maskContentUnits()->currentValue()->enumValue(); if (contentUnits == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y()); contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height()); } if (m_maskContentPicture) return m_maskContentPicture; SubtreeContentTransformScope contentTransformScope(contentTransformation); // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and // userSpaceOnUse units (http://crbug.com/294900). FloatRect bounds = strokeBoundingBox(); OwnPtr<DisplayItemList> displayItemList; if (RuntimeEnabledFeatures::slimmingPaintEnabled()) displayItemList = DisplayItemList::create(); GraphicsContext context(nullptr, displayItemList.get()); context.beginRecording(bounds); ColorFilter maskContentFilter = style()->svgStyle().colorInterpolation() == CI_LINEARRGB ? ColorFilterSRGBToLinearRGB : ColorFilterNone; context.setColorFilter(maskContentFilter); for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { LayoutObject* layoutObject = childElement->layoutObject(); if (!layoutObject) continue; const ComputedStyle* style = layoutObject->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; SVGPaintContext::paintSubtree(&context, layoutObject); } if (displayItemList) displayItemList->commitNewDisplayItemsAndReplay(context); m_maskContentPicture = context.endRecording(); return m_maskContentPicture; }
bool LayoutSVGResourceClipper::calculateClipContentPathIfNeeded() { if (!m_clipContentPath.isEmpty()) return true; // If the current clip-path gets clipped itself, we have to fallback to masking. if (style()->svgStyle().hasClipper()) return false; unsigned opCount = 0; bool usingBuilder = false; SkOpBuilder clipPathBuilder; for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { LayoutObject* childLayoutObject = childElement->layoutObject(); if (!childLayoutObject) continue; // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts. if (childLayoutObject->isSVGText()) { m_clipContentPath.clear(); return false; } if (!childElement->isSVGGraphicsElement()) continue; const ComputedStyle* style = childLayoutObject->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; // Current shape in clip-path gets clipped too. Fallback to masking. if (style->svgStyle().hasClipper()) { m_clipContentPath.clear(); return false; } // First clip shape. if (m_clipContentPath.isEmpty()) { if (isSVGGeometryElement(childElement)) toSVGGeometryElement(childElement)->toClipPath(m_clipContentPath); else if (isSVGUseElement(childElement)) toSVGUseElement(childElement)->toClipPath(m_clipContentPath); continue; } // Multiple shapes require PathOps. In some degenerate cases PathOps can exhibit quadratic // behavior, so we cap the number of ops to a reasonable count. const unsigned kMaxOps = 42; if (!RuntimeEnabledFeatures::pathOpsSVGClippingEnabled() || ++opCount > kMaxOps) { m_clipContentPath.clear(); return false; } // Second clip shape => start using the builder. if (!usingBuilder) { clipPathBuilder.add(m_clipContentPath.skPath(), kUnion_SkPathOp); usingBuilder = true; } Path subPath; if (isSVGGeometryElement(childElement)) toSVGGeometryElement(childElement)->toClipPath(subPath); else if (isSVGUseElement(childElement)) toSVGUseElement(childElement)->toClipPath(subPath); clipPathBuilder.add(subPath.skPath(), kUnion_SkPathOp); } if (usingBuilder) { SkPath resolvedPath; clipPathBuilder.resolve(&resolvedPath); m_clipContentPath = resolvedPath; } return true; }
PassRefPtr<const SkPicture> LayoutSVGResourceClipper::createContentPicture(AffineTransform& contentTransformation, const FloatRect& targetBoundingBox, GraphicsContext& context) { ASSERT(frame()); if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y()); contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height()); } if (m_clipContentPicture) return m_clipContentPicture; SubtreeContentTransformScope contentTransformScope(contentTransformation); // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and // userSpaceOnUse units (http://crbug.com/294900). FloatRect bounds = strokeBoundingBox(); SkPictureBuilder pictureBuilder(bounds, nullptr, &context); for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { LayoutObject* layoutObject = childElement->layoutObject(); if (!layoutObject) continue; const ComputedStyle* style = layoutObject->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; bool isUseElement = isSVGUseElement(*childElement); if (isUseElement) { const SVGGraphicsElement* clippingElement = toSVGUseElement(*childElement).targetGraphicsElementForClipping(); if (!clippingElement) continue; layoutObject = clippingElement->layoutObject(); if (!layoutObject) continue; } // Only shapes, paths and texts are allowed for clipping. if (!layoutObject->isSVGShape() && !layoutObject->isSVGText()) continue; if (isUseElement) layoutObject = childElement->layoutObject(); // Switch to a paint behavior where all children of this <clipPath> will be laid out using special constraints: // - fill-opacity/stroke-opacity/opacity set to 1 // - masker/filter not applied when laying out the children // - fill is set to the initial fill paint server (solid, black) // - stroke is set to the initial stroke paint server (none) PaintInfo info(pictureBuilder.context(), LayoutRect::infiniteIntRect(), PaintPhaseForeground, GlobalPaintNormalPhase, PaintLayerPaintingRenderingClipPathAsMask); layoutObject->paint(info, IntPoint()); } m_clipContentPicture = pictureBuilder.endRecording(); return m_clipContentPicture; }