bool RenderSVGTransformableContainer::calculateLocalTransform() { SVGGraphicsElement& element = graphicsElement(); // If we're either the renderer for a <use> element, or for any <g> element inside the shadow // tree, that was created during the use/symbol/svg expansion in SVGUseElement. These containers // need to respect the translations induced by their corresponding use elements x/y attributes. SVGUseElement* useElement = 0; if (isSVGUseElement(element)) useElement = &toSVGUseElement(element); else if (element.isInShadowTree() && isSVGGElement(element)) { SVGElement* correspondingElement = element.correspondingElement(); if (correspondingElement && isSVGUseElement(correspondingElement)) useElement = toSVGUseElement(correspondingElement); } if (useElement) { SVGLengthContext lengthContext(useElement); FloatSize translation(useElement->x().value(lengthContext), useElement->y().value(lengthContext)); if (translation != m_lastTranslation) m_needsTransformUpdate = true; m_lastTranslation = translation; } m_didTransformToRootUpdate = m_needsTransformUpdate || SVGRenderSupport::transformToRootChanged(parent()); if (!m_needsTransformUpdate) return false; m_localTransform = element.animatedLocalTransform(); m_localTransform.translate(m_lastTranslation.width(), m_lastTranslation.height()); m_needsTransformUpdate = false; return true; }
void SVGUseElement::buildInstanceTree(SVGElement* target, SVGElementInstance* targetInstance, bool& foundProblem, bool foundUse) { ASSERT(target); ASSERT(targetInstance); // Spec: If the referenced object is itself a 'use', or if there are 'use' subelements within the referenced // object, the instance tree will contain recursive expansion of the indirect references to form a complete tree. bool targetHasUseTag = target->hasTagName(SVGNames::useTag); SVGElement* newTarget = 0; if (targetHasUseTag) { foundProblem = hasCycleUseReferencing(toSVGUseElement(target), targetInstance, newTarget); if (foundProblem) return; // We only need to track first degree <use> dependencies. Indirect references are handled // as the invalidation bubbles up the dependency chain. if (!foundUse) { document().accessSVGExtensions()->addElementReferencingTarget(this, target); foundUse = true; } } else if (isDisallowedElement(*target)) { foundProblem = true; return; } // A general description from the SVG spec, describing what buildInstanceTree() actually does. // // Spec: If the 'use' element references a 'g' which contains two 'rect' elements, then the instance tree // contains three SVGElementInstance objects, a root SVGElementInstance object whose correspondingElement // is the SVGGElement object for the 'g', and then two child SVGElementInstance objects, each of which has // its correspondingElement that is an SVGRectElement object. for (auto& element : childrenOfType<SVGElement>(*target)) { // Skip any non-svg nodes or any disallowed element. if (isDisallowedElement(element)) continue; // Create SVGElementInstance object, for both container/non-container nodes. RefPtr<SVGElementInstance> instance = SVGElementInstance::create(this, 0, &element); SVGElementInstance* instancePtr = instance.get(); targetInstance->appendChild(instance.release()); // Enter recursion, appending new instance tree nodes to the "instance" object. buildInstanceTree(&element, instancePtr, foundProblem, foundUse); if (foundProblem) return; } if (!targetHasUseTag || !newTarget) return; RefPtr<SVGElementInstance> newInstance = SVGElementInstance::create(this, toSVGUseElement(target), newTarget); SVGElementInstance* newInstancePtr = newInstance.get(); targetInstance->appendChild(newInstance.release()); buildInstanceTree(newTarget, newInstancePtr, foundProblem, foundUse); }
inline EventTarget& eventTargetRespectingTargetRules(Node& referenceNode) { if (referenceNode.isPseudoElement()) { EventTarget* hostElement = toPseudoElement(referenceNode).hostElement(); ASSERT(hostElement); return *hostElement; } #if ENABLE(SVG) if (!referenceNode.isSVGElement() || !referenceNode.isInShadowTree()) return referenceNode; // Spec: The event handling for the non-exposed tree works as if the referenced element had been textually included // as a deeply cloned child of the 'use' element, except that events are dispatched to the SVGElementInstance objects Node* rootNode = referenceNode.treeScope().rootNode(); Element* shadowHostElement = rootNode->isShadowRoot() ? toShadowRoot(rootNode)->hostElement() : 0; // At this time, SVG nodes are not supported in non-<use> shadow trees. if (!shadowHostElement || !shadowHostElement->hasTagName(SVGNames::useTag)) return referenceNode; SVGUseElement* useElement = toSVGUseElement(shadowHostElement); if (SVGElementInstance* instance = useElement->instanceForShadowTreeElement(&referenceNode)) return *instance; #endif return referenceNode; }
void SVGElement::buildPendingResourcesIfNeeded() { Document& document = this->document(); if (!needsPendingResourceHandling() || !inDocument() || inUseShadowTree()) return; SVGDocumentExtensions& extensions = document.accessSVGExtensions(); AtomicString resourceId = getIdAttribute(); if (!extensions.hasPendingResource(resourceId)) return; // Mark pending resources as pending for removal. extensions.markPendingResourcesForRemoval(resourceId); // Rebuild pending resources for each client of a pending resource that is being removed. while (Element* clientElement = extensions.removeElementFromPendingResourcesForRemoval(resourceId)) { ASSERT(clientElement->hasPendingResources()); if (clientElement->hasPendingResources()) { // FIXME: Ideally we'd always resolve pending resources async instead of inside // insertedInto and svgAttributeChanged. For now we only do it for <use> since // that would stamp out DOM. if (isSVGUseElement(clientElement)) toSVGUseElement(clientElement)->invalidateShadowTree(); else clientElement->buildPendingResource(); extensions.clearHasPendingResourcesIfPossible(clientElement); } } }
String SVGElement::title() const { // According to spec, we should not return titles when hovering over root <svg> elements (those // <title> elements are the title of the document, not a tooltip) so we instantly return. if (isOutermostSVGSVGElement()) return String(); // Walk up the tree, to find out whether we're inside a <use> shadow tree, to find the right title. if (isInShadowTree()) { Element* shadowHostElement = toShadowRoot(treeScope().rootNode()).host(); // At this time, SVG nodes are not allowed in non-<use> shadow trees, so any shadow root we do // have should be a use. The assert and following test is here to catch future shadow DOM changes // that do enable SVG in a shadow tree. ASSERT(!shadowHostElement || isSVGUseElement(*shadowHostElement)); if (isSVGUseElement(shadowHostElement)) { SVGUseElement& useElement = toSVGUseElement(*shadowHostElement); // If the <use> title is not empty we found the title to use. String useTitle(useElement.title()); if (!useTitle.isEmpty()) return useTitle; } } // If we aren't an instance in a <use> or the <use> title was not found, then find the first // <title> child of this element. // If a title child was found, return the text contents. if (Element* titleElement = Traversal<SVGTitleElement>::firstChild(*this)) return titleElement->innerText(); // Otherwise return a null/empty string. return String(); }
SVGUseElement* SVGElement::correspondingUseElement() const { if (ShadowRoot* root = containingShadowRoot()) { if (isSVGUseElement(root->host()) && (root->type() == ShadowRootType::UserAgent)) return toSVGUseElement(root->host()); } return nullptr; }
bool SVGUseElement::instanceTreeIsLoading(const SVGElement* targetInstance) { for (const SVGElement* element = targetInstance; element; element = Traversal<SVGElement>::next(*element, targetInstance)) { if (isSVGUseElement(*element) && toSVGUseElement(*element).resourceIsStillLoading()) return true; } return false; }
void SVGUseElement::expandUseElementsInShadowTree(Node* element) { // Why expand the <use> elements in the shadow tree here, and not just // do this directly in buildShadowTree, if we encounter a <use> element? // // Short answer: Because we may miss to expand some elements. Ie. if a <symbol> // contains <use> tags, we'd miss them. So once we're done with settin' up the // actual shadow tree (after the special case modification for svg/symbol) we have // to walk it completely and expand all <use> elements. if (element->hasTagName(SVGNames::useTag)) { SVGUseElement* use = toSVGUseElement(element); ASSERT(!use->cachedDocumentIsStillLoading()); ASSERT(referencedDocument()); Element* targetElement = SVGURIReference::targetElementFromIRIString(use->href(), *referencedDocument()); SVGElement* target = 0; if (targetElement && targetElement->isSVGElement()) target = toSVGElement(targetElement); // Don't ASSERT(target) here, it may be "pending", too. // Setup sub-shadow tree root node RefPtr<SVGGElement> cloneParent = SVGGElement::create(SVGNames::gTag, *referencedDocument()); use->cloneChildNodes(cloneParent.get()); // Spec: In the generated content, the 'use' will be replaced by 'g', where all attributes from the // 'use' element except for x, y, width, height and xlink:href are transferred to the generated 'g' element. transferUseAttributesToReplacedElement(use, cloneParent.get()); if (target && !isDisallowedElement(*target)) { RefPtr<Element> newChild = target->cloneElementWithChildren(); ASSERT(newChild->isSVGElement()); cloneParent->appendChild(newChild.release()); } // We don't walk the target tree element-by-element, and clone each element, // but instead use cloneElementWithChildren(). This is an optimization for the common // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). // Though if there are disallowed elements in the subtree, we have to remove them. // For instance: <use> on <g> containing <foreignObject> (indirect case). if (subtreeContainsDisallowedElement(*cloneParent)) removeDisallowedElementsFromSubtree(*cloneParent); RefPtr<Node> replacingElement(cloneParent.get()); // Replace <use> with referenced content. ASSERT(use->parentNode()); use->parentNode()->replaceChild(cloneParent.release(), use); // Expand the siblings because the *element* is replaced and we will // lose the sibling chain when we are back from recursion. element = replacingElement.get(); for (RefPtr<Node> sibling = element->nextSibling(); sibling; sibling = sibling->nextSibling()) expandUseElementsInShadowTree(sibling.get()); } for (RefPtr<Node> child = element->firstChild(); child; child = child->nextSibling()) expandUseElementsInShadowTree(child.get()); }
void RenderSVGResourceClipper::createDisplayList(GraphicsContext* context, const AffineTransform& contentTransformation) { ASSERT(context); ASSERT(frame()); // 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(); context->beginRecording(bounds); // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints: // - fill-opacity/stroke-opacity/opacity set to 1 // - masker/filter not applied when rendering the children // - fill is set to the initial fill paint server (solid, black) // - stroke is set to the initial stroke paint server (none) PaintBehavior oldBehavior = frame()->view()->paintBehavior(); frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask); for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { RenderObject* renderer = childElement->renderer(); if (!renderer) continue; RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; WindRule newClipRule = style->svgStyle().clipRule(); bool isUseElement = isSVGUseElement(*childElement); if (isUseElement) { SVGUseElement& useElement = toSVGUseElement(*childElement); renderer = useElement.rendererClipChild(); if (!renderer) continue; if (!useElement.hasAttribute(SVGNames::clip_ruleAttr)) newClipRule = renderer->style()->svgStyle().clipRule(); } // Only shapes, paths and texts are allowed for clipping. if (!renderer->isSVGShape() && !renderer->isSVGText()) continue; context->setFillRule(newClipRule); if (isUseElement) renderer = childElement->renderer(); SVGRenderingContext::renderSubtree(context, renderer, contentTransformation); } frame()->view()->setPaintBehavior(oldBehavior); m_clipContentDisplayList = context->endRecording(); }
PassRefPtr<DisplayList> RenderSVGResourceClipper::asDisplayList(GraphicsContext* context, const AffineTransform& contentTransformation) { ASSERT(context); ASSERT(frame()); context->beginRecording(repaintRectInLocalCoordinates()); // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints: // - fill-opacity/stroke-opacity/opacity set to 1 // - masker/filter not applied when rendering the children // - fill is set to the initial fill paint server (solid, black) // - stroke is set to the initial stroke paint server (none) PaintBehavior oldBehavior = frame()->view()->paintBehavior(); frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask); for (Node* childNode = element()->firstChild(); childNode; childNode = childNode->nextSibling()) { RenderObject* renderer = childNode->renderer(); if (!childNode->isSVGElement() || !renderer) continue; RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; WindRule newClipRule = style->svgStyle()->clipRule(); bool isUseElement = childNode->hasTagName(SVGNames::useTag); if (isUseElement) { SVGUseElement* useElement = toSVGUseElement(childNode); renderer = useElement->rendererClipChild(); if (!renderer) continue; if (!useElement->hasAttribute(SVGNames::clip_ruleAttr)) newClipRule = renderer->style()->svgStyle()->clipRule(); } // Only shapes, paths and texts are allowed for clipping. if (!renderer->isSVGShape() && !renderer->isSVGText()) continue; context->setFillRule(newClipRule); if (isUseElement) renderer = childNode->renderer(); SVGRenderingContext::renderSubtree(context, renderer, contentTransformation); } frame()->view()->setPaintBehavior(oldBehavior); return context->endRecording(); }
static void dumpInstanceTree(unsigned int& depth, String& text, SVGElementInstance* targetInstance) { SVGElement* element = targetInstance->correspondingElement(); ASSERT(element); if (element->hasTagName(SVGNames::useTag)) { if (toSVGUseElement(element)->cachedDocumentIsStillLoading()) return; } SVGElement* shadowTreeElement = targetInstance->shadowTreeElement(); ASSERT(shadowTreeElement); SVGUseElement* directUseElement = targetInstance->directUseElement(); String directUseElementName = directUseElement ? directUseElement->nodeName() : "null"; String elementId = element->getIdAttribute(); String elementNodeName = element->nodeName(); String shadowTreeElementNodeName = shadowTreeElement->nodeName(); String parentNodeName = element->parentNode() ? element->parentNode()->nodeName() : "null"; String firstChildNodeName = element->firstChild() ? element->firstChild()->nodeName() : "null"; for (unsigned int i = 0; i < depth; ++i) text += " "; text += String::format("SVGElementInstance this=%p, (parentNode=%s (%p), firstChild=%s (%p), correspondingElement=%s (%p), directUseElement=%s (%p), shadowTreeElement=%s (%p), id=%s)\n", targetInstance, parentNodeName.latin1().data(), element->parentNode(), firstChildNodeName.latin1().data(), element->firstChild(), elementNodeName.latin1().data(), element, directUseElementName.latin1().data(), directUseElement, shadowTreeElementNodeName.latin1().data(), shadowTreeElement, elementId.latin1().data()); for (unsigned int i = 0; i < depth; ++i) text += " "; const HashSet<SVGElementInstance*>& elementInstances = element->instancesForElement(); text += "Corresponding element is associated with " + String::number(elementInstances.size()) + " instance(s):\n"; const HashSet<SVGElementInstance*>::const_iterator end = elementInstances.end(); for (HashSet<SVGElementInstance*>::const_iterator it = elementInstances.begin(); it != end; ++it) { for (unsigned int i = 0; i < depth; ++i) text += " "; text += String::format(" -> SVGElementInstance this=%p, (refCount: %i, shadowTreeElement in document? %i)\n", *it, (*it)->refCount(), (*it)->shadowTreeElement()->inDocument()); } ++depth; for (SVGElementInstance* instance = targetInstance->firstChild(); instance; instance = instance->nextSibling()) dumpInstanceTree(depth, text, instance); --depth; }
bool LayoutSVGTransformableContainer::calculateLocalTransform() { SVGGraphicsElement* element = toSVGGraphicsElement(this->element()); ASSERT(element); // If we're either the layoutObject for a <use> element, or for any <g> element inside the shadow // tree, that was created during the use/symbol/svg expansion in SVGUseElement. These containers // need to respect the translations induced by their corresponding use elements x/y attributes. SVGUseElement* useElement = nullptr; if (isSVGUseElement(*element)) { useElement = toSVGUseElement(element); } else if (isSVGGElement(*element) && toSVGGElement(element)->inUseShadowTree()) { SVGElement* correspondingElement = element->correspondingElement(); if (isSVGUseElement(correspondingElement)) useElement = toSVGUseElement(correspondingElement); } if (useElement) { SVGLengthContext lengthContext(useElement); FloatSize translation( useElement->x()->currentValue()->value(lengthContext), useElement->y()->currentValue()->value(lengthContext)); if (translation != m_additionalTranslation) m_needsTransformUpdate = true; m_additionalTranslation = translation; } m_didTransformToRootUpdate = m_needsTransformUpdate || SVGLayoutSupport::transformToRootChanged(parent()); if (!m_needsTransformUpdate) return false; m_localTransform = element->calculateAnimatedLocalTransform(); m_localTransform.translate(m_additionalTranslation.width(), m_additionalTranslation.height()); m_needsTransformUpdate = false; return true; }
EventTarget* EventPath::eventTargetRespectingTargetRules(Node* referenceNode) { ASSERT(referenceNode); if (referenceNode->isPseudoElement()) return referenceNode->parentNode(); if (!usesDeprecatedSVGUseTreeEventRules(referenceNode)) return referenceNode; // Spec: The event handling for the non-exposed tree works as if the referenced element had been textually included // as a deeply cloned child of the 'use' element, except that events are dispatched to the SVGElementInstance objects. Node& rootNode = referenceNode->treeScope().rootNode(); Element* shadowHostElement = rootNode.isShadowRoot() ? toShadowRoot(rootNode).host() : 0; // At this time, SVG nodes are not supported in non-<use> shadow trees. if (!isSVGUseElement(shadowHostElement)) return referenceNode; SVGUseElement& useElement = toSVGUseElement(*shadowHostElement); if (SVGElementInstance* instance = useElement.instanceForShadowTreeElement(referenceNode)) return instance; return referenceNode; }
bool SVGUseElement::expandUseElementsInShadowTree(SVGElement* element) { ASSERT(element); // Why expand the <use> elements in the shadow tree here, and not just // do this directly in buildShadowTree, if we encounter a <use> element? // // Short answer: Because we may miss to expand some elements. For example, if a <symbol> // contains <use> tags, we'd miss them. So once we're done with setting up the // actual shadow tree (after the special case modification for svg/symbol) we have // to walk it completely and expand all <use> elements. if (isSVGUseElement(*element)) { SVGUseElement* use = toSVGUseElement(element); ASSERT(!use->resourceIsStillLoading()); SVGElement* target = 0; if (hasCycleUseReferencing(toSVGUseElement(use->correspondingElement()), use, target)) return false; if (target && isDisallowedElement(target)) return false; // Don't ASSERT(target) here, it may be "pending", too. // Setup sub-shadow tree root node RefPtrWillBeRawPtr<SVGGElement> cloneParent = SVGGElement::create(referencedScope()->document()); cloneParent->setCorrespondingElement(use->correspondingElement()); // Move already cloned elements to the new <g> element for (RefPtrWillBeRawPtr<Node> child = use->firstChild(); child; ) { RefPtrWillBeRawPtr<Node> nextChild = child->nextSibling(); cloneParent->appendChild(child); child = nextChild.release(); } // Spec: In the generated content, the 'use' will be replaced by 'g', where all attributes from the // 'use' element except for x, y, width, height and xlink:href are transferred to the generated 'g' element. transferUseAttributesToReplacedElement(use, cloneParent.get()); if (target) { RefPtrWillBeRawPtr<Node> newChild = cloneNodeAndAssociate(*target); ASSERT(newChild->isSVGElement()); transferUseWidthAndHeightIfNeeded(*use, toSVGElement(newChild.get()), *target); cloneParent->appendChild(newChild.release()); } // We don't walk the target tree element-by-element, and clone each element, // but instead use cloneElementWithChildren(). This is an optimization for the common // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). // Though if there are disallowed elements in the subtree, we have to remove them. // For instance: <use> on <g> containing <foreignObject> (indirect case). if (subtreeContainsDisallowedElement(cloneParent.get())) removeDisallowedElementsFromSubtree(*cloneParent); RefPtrWillBeRawPtr<SVGElement> replacingElement(cloneParent.get()); // Replace <use> with referenced content. ASSERT(use->parentNode()); use->parentNode()->replaceChild(cloneParent.release(), use); // Expand the siblings because the *element* is replaced and we will // lose the sibling chain when we are back from recursion. element = replacingElement.get(); for (RefPtrWillBeRawPtr<SVGElement> sibling = Traversal<SVGElement>::nextSibling(*element); sibling; sibling = Traversal<SVGElement>::nextSibling(*sibling)) { if (!expandUseElementsInShadowTree(sibling.get())) return false; } } for (RefPtrWillBeRawPtr<SVGElement> child = Traversal<SVGElement>::firstChild(*element); child; child = Traversal<SVGElement>::nextSibling(*child)) { if (!expandUseElementsInShadowTree(child.get())) return false; } return true; }
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; }
bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox) { ASSERT(clipperData); ASSERT(clipperData->clipMaskImage); GraphicsContext* maskContext = clipperData->clipMaskImage->context(); ASSERT(maskContext); AffineTransform maskContentTransformation; if (clipPathElement().clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y()); maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); maskContext->concatCTM(maskContentTransformation); } // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints: // - fill-opacity/stroke-opacity/opacity set to 1 // - masker/filter not applied when rendering the children // - fill is set to the initial fill paint server (solid, black) // - stroke is set to the initial stroke paint server (none) PaintBehavior oldBehavior = view().frameView().paintBehavior(); view().frameView().setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask); // Draw all clipPath children into a global mask. auto children = childrenOfType<SVGElement>(clipPathElement()); for (auto it = children.begin(), end = children.end(); it != end; ++it) { SVGElement& child = *it; auto renderer = child.renderer(); if (!renderer) continue; if (renderer->needsLayout()) { view().frameView().setPaintBehavior(oldBehavior); return false; } RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; WindRule newClipRule = style->svgStyle()->clipRule(); bool isUseElement = child.hasTagName(SVGNames::useTag); if (isUseElement) { SVGUseElement& useElement = toSVGUseElement(child); renderer = useElement.rendererClipChild(); if (!renderer) continue; if (!useElement.hasAttribute(SVGNames::clip_ruleAttr)) newClipRule = renderer->style()->svgStyle()->clipRule(); } // Only shapes, paths and texts are allowed for clipping. if (!renderer->isSVGShape() && !renderer->isSVGText()) continue; maskContext->setFillRule(newClipRule); // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule. // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering. // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above. SVGRenderingContext::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? *child.renderer() : *renderer, maskContentTransformation); } view().frameView().setPaintBehavior(oldBehavior); return true; }