bool LayoutSVGResourceClipper::hitTestClipContent( const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint) { FloatPoint point = nodeAtPoint; if (!SVGLayoutSupport::pointInClippingArea(*this, point)) return false; if (clipPathUnits() == SVGUnitTypes::kSvgUnitTypeObjectboundingbox) { AffineTransform transform; transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); point = transform.inverse().mapPoint(point); } AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->calculateTransform( SVGElement::IncludeMotionTransform); if (!animatedLocalTransform.isInvertible()) return false; point = animatedLocalTransform.inverse().mapPoint(point); for (const SVGElement& childElement : Traversal<SVGElement>::childrenOf(*element())) { if (!contributesToClip(childElement)) continue; IntPoint hitPoint; HitTestResult result(HitTestRequest::SVGClipContent, hitPoint); LayoutObject* layoutObject = childElement.layoutObject(); if (layoutObject->nodeAtFloatPoint(result, point, HitTestForeground)) return true; } return false; }
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; }
SVGResource* SVGClipPathElement::canvasResource() { if (!m_clipper) m_clipper = SVGResourceClipper::create(); else m_clipper->resetClipData(); bool bbox = clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; RenderStyle* clipPathStyle = styleForRenderer(parent()->renderer()); // FIXME: Manual style resolution is a hack for (Node* n = firstChild(); n; n = n->nextSibling()) { if (n->isSVGElement() && static_cast<SVGElement*>(n)->isStyledTransformable()) { SVGStyledTransformableElement* styled = static_cast<SVGStyledTransformableElement*>(n); RenderStyle* pathStyle = document()->styleSelector()->styleForElement(styled, clipPathStyle); if (pathStyle->display() != NONE) { Path pathData = styled->toClipPath(); // FIXME: How do we know the element has done a layout? pathData.transform(styled->animatedLocalTransform()); if (!pathData.isEmpty()) m_clipper->addClipData(pathData, pathStyle->svgStyle()->clipRule(), bbox); } pathStyle->deref(document()->renderArena()); } } if (m_clipper->clipData().isEmpty()) { Path pathData; pathData.addRect(FloatRect()); m_clipper->addClipData(pathData, RULE_EVENODD, bbox); } clipPathStyle->deref(document()->renderArena()); return m_clipper.get(); }
void RenderSVGResourceClipper::drawClipMaskContent(GraphicsContext* context, const FloatRect& targetBoundingBox) { ASSERT(context); AffineTransform contentTransformation; if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y()); contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height()); context->concatCTM(contentTransformation); } if (!m_clipContentDisplayList) createDisplayList(context, contentTransformation); ASSERT(m_clipContentDisplayList); context->drawDisplayList(m_clipContentDisplayList.get()); }
FloatRect LayoutSVGResourceClipper::resourceBoundingBox( const FloatRect& referenceBox) { // The resource has not been layouted yet. Return the reference box. if (selfNeedsLayout()) return referenceBox; if (m_localClipBounds.isEmpty()) calculateLocalClipBounds(); AffineTransform transform = toSVGClipPathElement(element())->calculateTransform( SVGElement::IncludeMotionTransform); if (clipPathUnits() == SVGUnitTypes::kSvgUnitTypeObjectboundingbox) { transform.translate(referenceBox.x(), referenceBox.y()); transform.scaleNonUniform(referenceBox.width(), referenceBox.height()); } return transform.mapRect(m_localClipBounds); }
FloatRect LayoutSVGResourceClipper::resourceBoundingBox(const LayoutObject* object) { // Resource was not layouted yet. Give back the boundingBox of the object. if (selfNeedsLayout()) return object->objectBoundingBox(); if (m_clipBoundaries.isEmpty()) calculateClipContentPaintInvalidationRect(); if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { FloatRect objectBoundingBox = object->objectBoundingBox(); AffineTransform transform; transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); return transform.mapRect(m_clipBoundaries); } return m_clipBoundaries; }
bool LayoutSVGResourceClipper::asPath(const AffineTransform& animatedLocalTransform, const FloatRect& referenceBox, Path& clipPath) { if (!calculateClipContentPathIfNeeded()) return false; clipPath = m_clipContentPath; // We are able to represent the clip as a path. Continue with direct clipping, // and transform the content to userspace if necessary. if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { AffineTransform transform; transform.translate(referenceBox.x(), referenceBox.y()); transform.scaleNonUniform(referenceBox.width(), referenceBox.height()); clipPath.transform(transform); } // Transform path by animatedLocalTransform. clipPath.transform(animatedLocalTransform); 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; }
bool RenderSVGResourceClipper::tryPathOnlyClipping(GraphicsContext* context, const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox) { // If the current clip-path gets clipped itself, we have to fallback to masking. if (!style()->svgStyle().clipperResource().isEmpty()) return false; WindRule clipRule = RULE_NONZERO; Path clipPath = Path(); for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { RenderObject* renderer = childElement->renderer(); if (!renderer) continue; // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts. if (renderer->isSVGText()) return false; if (!childElement->isSVGGraphicsElement()) continue; SVGGraphicsElement* styled = toSVGGraphicsElement(childElement); RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; const SVGRenderStyle& svgStyle = style->svgStyle(); // Current shape in clip-path gets clipped too. Fallback to masking. if (!svgStyle.clipperResource().isEmpty()) return false; if (clipPath.isEmpty()) { // First clip shape. styled->toClipPath(clipPath); clipRule = svgStyle.clipRule(); clipPath.setWindRule(clipRule); continue; } if (RuntimeEnabledFeatures::pathOpsSVGClippingEnabled()) { // Attempt to generate a combined clip path, fall back to masking if not possible. Path subPath; styled->toClipPath(subPath); subPath.setWindRule(svgStyle.clipRule()); if (!clipPath.unionPath(subPath)) return false; } else { return false; } } // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary. if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { AffineTransform transform; transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); clipPath.transform(transform); } // Transform path by animatedLocalTransform. clipPath.transform(animatedLocalTransform); // The SVG specification wants us to clip everything, if clip-path doesn't have a child. if (clipPath.isEmpty()) clipPath.addRect(FloatRect()); context->clipPath(clipPath, clipRule); return true; }
bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* target, const FloatRect& targetBoundingBox, const FloatRect& paintInvalidationRect, GraphicsContext* context, ClipperState& clipperState) { ASSERT(target); ASSERT(context); ASSERT(clipperState == ClipperNotApplied); ASSERT_WITH_SECURITY_IMPLICATION(!needsLayout()); if (paintInvalidationRect.isEmpty() || m_inClipExpansion) return false; TemporaryChange<bool> inClipExpansionChange(m_inClipExpansion, true); AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->animatedLocalTransform(); // When drawing a clip for non-SVG elements, the CTM does not include the zoom factor. // In this case, we need to apply the zoom scale explicitly - but only for clips with // userSpaceOnUse units (the zoom is accounted for objectBoundingBox-resolved lengths). if (!target->isSVG() && clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) { ASSERT(style()); animatedLocalTransform.scale(style()->effectiveZoom()); } // First, try to apply the clip as a clipPath. if (tryPathOnlyClipping(context, animatedLocalTransform, targetBoundingBox)) { clipperState = ClipperAppliedPath; return true; } // Fall back to masking. clipperState = ClipperAppliedMask; // Mask layer start context->beginTransparencyLayer(1, &paintInvalidationRect); { GraphicsContextStateSaver maskContentSaver(*context); context->concatCTM(animatedLocalTransform); // clipPath can also be clipped by another clipPath. SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this); RenderSVGResourceClipper* clipPathClipper = resources ? resources->clipper() : 0; ClipperState clipPathClipperState = ClipperNotApplied; if (clipPathClipper && !clipPathClipper->applyClippingToContext(this, targetBoundingBox, paintInvalidationRect, context, clipPathClipperState)) { // FIXME: Awkward state micro-management. Ideally, GraphicsContextStateSaver should // a) pop saveLayers also // b) pop multiple states if needed (similarly to SkCanvas::restoreToCount()) // Then we should be able to replace this mess with a single, top-level GCSS. maskContentSaver.restore(); context->restoreLayer(); return false; } drawClipMaskContent(context, targetBoundingBox); if (clipPathClipper) clipPathClipper->postApplyStatefulResource(this, context, clipPathClipperState); } // Masked content layer start. context->beginLayer(1, CompositeSourceIn, &paintInvalidationRect); return true; }