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; }
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; }