void SVGAnimateTransformElement::applyResultsToTarget() { if (!hasValidTarget()) return; // We accumulate to the target element transform list so there is not much to do here. SVGElement* targetElement = this->targetElement(); if (!targetElement) return; if (RenderObject* renderer = targetElement->renderer()) { renderer->setNeedsTransformUpdate(); RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer); } // ...except in case where we have additional instances in <use> trees. const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement(); RefPtr<SVGTransformList> transformList = transformListFor(targetElement); const HashSet<SVGElementInstance*>::const_iterator end = instances.end(); for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) { SVGElement* shadowTreeElement = (*it)->shadowTreeElement(); ASSERT(shadowTreeElement); if (shadowTreeElement->isStyledTransformable()) static_cast<SVGStyledTransformableElement*>(shadowTreeElement)->setTransformBaseValue(transformList.get()); else if (shadowTreeElement->hasTagName(SVGNames::textTag)) static_cast<SVGTextElement*>(shadowTreeElement)->setTransformBaseValue(transformList.get()); else if (shadowTreeElement->hasTagName(SVGNames::linearGradientTag) || shadowTreeElement->hasTagName(SVGNames::radialGradientTag)) static_cast<SVGGradientElement*>(shadowTreeElement)->setGradientTransformBaseValue(transformList.get()); if (RenderObject* renderer = shadowTreeElement->renderer()) { renderer->setNeedsTransformUpdate(); RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer); } } }
void SVGAnimateMotionElement::applyResultsToTarget() { // We accumulate to the target element transform list so there is not much to do here. SVGElement* targetElement = this->targetElement(); if (!targetElement) return; if (RenderObject* renderer = targetElement->renderer()) RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer); AffineTransform* t = targetElement->supplementalTransform(); if (!t) return; // ...except in case where we have additional instances in <use> trees. const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement(); const HashSet<SVGElementInstance*>::const_iterator end = instances.end(); for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) { SVGElement* shadowTreeElement = (*it)->shadowTreeElement(); ASSERT(shadowTreeElement); AffineTransform* transform = shadowTreeElement->supplementalTransform(); if (!transform) continue; transform->setMatrix(t->a(), t->b(), t->c(), t->d(), t->e(), t->f()); if (RenderObject* renderer = shadowTreeElement->renderer()) { renderer->setNeedsTransformUpdate(); RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer); } } }
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(); }
bool RenderSVGModelObject::checkEnclosure(RenderObject* renderer, const FloatRect& rect) { if (!renderer || renderer->style()->pointerEvents() == PE_NONE) return false; if (!isGraphicsElement(renderer)) return false; AffineTransform ctm; SVGElement* svgElement = toSVGElement(renderer->node()); getElementCTM(svgElement, ctm); ASSERT(svgElement->renderer()); return rect.contains(ctm.mapRect(svgElement->renderer()->repaintRectInLocalCoordinates())); }
bool RenderSVGModelObject::checkIntersection(RenderObject* renderer, const SVGRect& rect) { if (!renderer || renderer->style()->pointerEvents() == PE_NONE) return false; if (!isGraphicsElement(renderer)) return false; AffineTransform ctm; SVGElement* svgElement = toSVGElement(renderer->node()); getElementCTM(svgElement, ctm); ASSERT(svgElement->renderer()); return intersectsAllowingEmpty(rect, ctm.mapRect(svgElement->renderer()->repaintRectInLocalCoordinates())); }
bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint) { FloatPoint point = nodeAtPoint; if (!SVGRenderSupport::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())->animatedLocalTransform(); if (!animatedLocalTransform.isInvertible()) return false; point = animatedLocalTransform.inverse().mapPoint(point); for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { RenderObject* renderer = childElement->renderer(); if (!renderer) continue; if (!renderer->isSVGShape() && !renderer->isSVGText() && !isSVGUseElement(*childElement)) continue; IntPoint hitPoint; HitTestResult result(hitPoint); if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent), result, point, HitTestForeground)) return true; } return false; }
PassRefPtrWillBeRawPtr<SVGFilterBuilder> RenderSVGResourceFilter::buildPrimitives(SVGFilter* filter) { SVGFilterElement* filterElement = toSVGFilterElement(element()); FloatRect targetBoundingBox = filter->targetBoundingBox(); // Add effects to the builder RefPtrWillBeRawPtr<SVGFilterBuilder> builder = SVGFilterBuilder::create(SourceGraphic::create(filter), SourceAlpha::create(filter)); for (SVGElement* element = Traversal<SVGElement>::firstChild(*filterElement); element; element = Traversal<SVGElement>::nextSibling(*element)) { if (!element->isFilterEffect() || !element->renderer()) continue; SVGFilterPrimitiveStandardAttributes* effectElement = static_cast<SVGFilterPrimitiveStandardAttributes*>(element); RefPtrWillBeRawPtr<FilterEffect> effect = effectElement->build(builder.get(), filter); if (!effect) { builder->clearEffects(); return nullptr; } builder->appendEffectToEffectReferences(effect, effectElement->renderer()); effectElement->setStandardAttributes(effect.get()); effect->setEffectBoundaries(SVGLengthContext::resolveRectangle<SVGFilterPrimitiveStandardAttributes>(effectElement, filterElement->primitiveUnits()->currentValue()->enumValue(), targetBoundingBox)); effect->setOperatingColorSpace( effectElement->renderer()->style()->svgStyle().colorInterpolationFilters() == CI_LINEARRGB ? ColorSpaceLinearRGB : ColorSpaceDeviceRGB); builder->add(AtomicString(effectElement->result()->currentValue()->value()), effect); } return builder.release(); }
void SVGAnimateMotionElement::applyResultsToTarget() { // We accumulate to the target element transform list so there is not much to do here. SVGElement* targetElement = this->targetElement(); if (!targetElement) return; if (RenderElement* renderer = targetElement->renderer()) RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer); AffineTransform* targetSupplementalTransform = targetElement->supplementalTransform(); if (!targetSupplementalTransform) return; // ...except in case where we have additional instances in <use> trees. for (auto* instance : targetElement->instances()) { AffineTransform* transform = instance->supplementalTransform(); if (!transform || *transform == *targetSupplementalTransform) continue; *transform = *targetSupplementalTransform; if (RenderElement* renderer = instance->renderer()) { renderer->setNeedsTransformUpdate(); RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer); } } }
void SVGStyledElement::rebuildRenderer() const { if (!renderer() || !renderer()->isRenderPath()) return; RenderPath* renderPath = static_cast<RenderPath*>(renderer()); SVGElement* parentElement = svg_dynamic_cast(parentNode()); if (parentElement && parentElement->renderer() && parentElement->isStyled() && parentElement->childShouldCreateRenderer(const_cast<SVGStyledElement*>(this))) renderPath->setNeedsLayout(true); }
void RenderSVGResourceMasker::calculateMaskContentPaintInvalidationRect() { 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; m_maskContentBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->paintInvalidationRectInLocalCoordinates())); } }
void SVGAnimateTransformElement::applyResultsToTarget() { if (!hasValidTarget()) return; // We accumulate to the target element transform list so there is not much to do here. SVGElement* targetElement = this->targetElement(); if (targetElement->renderer()) targetElement->renderer()->setNeedsLayout(true); // ...except in case where we have additional instances in <use> trees. const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement(); RefPtr<SVGTransformList> transformList = transformListFor(targetElement); const HashSet<SVGElementInstance*>::const_iterator end = instances.end(); for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) { SVGElement* shadowTreeElement = (*it)->shadowTreeElement(); ASSERT(shadowTreeElement); if (shadowTreeElement->isStyledTransformable()) static_cast<SVGStyledTransformableElement*>(shadowTreeElement)->setTransformBaseValue(transformList.get()); else if (shadowTreeElement->hasTagName(SVGNames::textTag)) static_cast<SVGTextElement*>(shadowTreeElement)->setTransformBaseValue(transformList.get()); if (shadowTreeElement->renderer()) shadowTreeElement->renderer()->setNeedsLayout(true); } }
void RenderSVGResourceClipper::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)) { RenderObject* renderer = childElement->renderer(); if (!renderer) continue; if (!renderer->isSVGShape() && !renderer->isSVGText() && !isSVGUseElement(*childElement)) continue; RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->paintInvalidationRectInLocalCoordinates())); } m_clipBoundaries = toSVGClipPathElement(element())->animatedLocalTransform().mapRect(m_clipBoundaries); }
static void getElementCTM(SVGElement* element, AffineTransform& transform) { ASSERT(element); element->document()->updateLayoutIgnorePendingStylesheets(); SVGElement* stopAtElement = SVGLocatable::nearestViewportElement(element); ASSERT(stopAtElement); Node* current = element; while (current && current->isSVGElement()) { SVGElement* currentElement = static_cast<SVGElement*>(current); if (currentElement->isStyled()) transform = const_cast<AffineTransform&>(currentElement->renderer()->localToParentTransform()).multiply(transform); // For getCTM() computation, stop at the nearest viewport element if (currentElement == stopAtElement) break; current = current->parentOrHostNode(); } }
void RenderSVGResourceMasker::createDisplayList(GraphicsContext* context, const AffineTransform& contentTransform) { ASSERT(context); // 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); 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; SVGRenderingContext::renderSubtree(context, renderer, contentTransform); } m_maskContentDisplayList = context->endRecording(); }
void SVGAnimateMotionElement::calculateAnimatedValue(float percentage, unsigned, SVGSMILElement*) { SVGElement* targetElement = this->targetElement(); if (!targetElement) return; AffineTransform* transform = targetElement->supplementalTransform(); if (!transform) return; if (RenderObject* targetRenderer = targetElement->renderer()) targetRenderer->setNeedsTransformUpdate(); if (!isAdditive()) transform->makeIdentity(); // FIXME: Implement accumulate. if (animationMode() == PathAnimation) { ASSERT(!animationPath().isEmpty()); Path path = animationPath(); float positionOnPath = path.length() * percentage; bool ok; FloatPoint position = path.pointAtLength(positionOnPath, ok); if (ok) { transform->translate(position.x(), position.y()); RotateMode rotateMode = this->rotateMode(); if (rotateMode == RotateAuto || rotateMode == RotateAutoReverse) { float angle = path.normalAngleAtLength(positionOnPath, ok); if (rotateMode == RotateAutoReverse) angle += 180; transform->rotate(angle); } } return; } FloatSize diff = m_toPoint - m_fromPoint; transform->translate(diff.width() * percentage + m_fromPoint.x(), diff.height() * percentage + m_fromPoint.y()); }
void SVGAnimateMotionElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement*) { SVGElement* targetElement = this->targetElement(); if (!targetElement) return; AffineTransform* transform = targetElement->supplementalTransform(); if (!transform) return; if (RenderObject* targetRenderer = targetElement->renderer()) targetRenderer->setNeedsTransformUpdate(); if (!isAdditive()) transform->makeIdentity(); if (animationMode() != PathAnimation) { FloatPoint toPointAtEndOfDuration = m_toPoint; if (isAccumulated() && repeatCount && m_hasToPointAtEndOfDuration) toPointAtEndOfDuration = m_toPointAtEndOfDuration; float animatedX = 0; animateAdditiveNumber(percentage, repeatCount, m_fromPoint.x(), m_toPoint.x(), toPointAtEndOfDuration.x(), animatedX); float animatedY = 0; animateAdditiveNumber(percentage, repeatCount, m_fromPoint.y(), m_toPoint.y(), toPointAtEndOfDuration.y(), animatedY); transform->translate(animatedX, animatedY); return; } buildTransformForProgress(transform, percentage); // Handle accumulate="sum". if (isAccumulated() && repeatCount) { for (unsigned i = 0; i < repeatCount; ++i) buildTransformForProgress(transform, 1); } }
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; }