bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* target, const FloatRect& targetBoundingBox, const FloatRect& repaintRect, GraphicsContext* context, ClipperContext& clipperContext) { ASSERT(target); ASSERT(context); ASSERT(clipperContext.state == ClipperContext::NotAppliedState); ASSERT_WITH_SECURITY_IMPLICATION(!needsLayout()); if (repaintRect.isEmpty() || m_inClipExpansion) return false; TemporaryChange<bool> inClipExpansionChange(m_inClipExpansion, true); // First, try to apply the clip as a clipPath. AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->animatedLocalTransform(); if (tryPathOnlyClipping(context, animatedLocalTransform, targetBoundingBox)) { clipperContext.state = ClipperContext::AppliedPathState; return true; } // Fall back to masking. clipperContext.state = ClipperContext::AppliedMaskState; // Mask layer start context->beginTransparencyLayer(1, &repaintRect); { GraphicsContextStateSaver maskContentSaver(*context); context->concatCTM(animatedLocalTransform); // clipPath can also be clipped by another clipPath. SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this); RenderSVGResourceClipper* clipPathClipper = 0; ClipperContext clipPathClipperContext; if (resources && (clipPathClipper = resources->clipper())) { if (!clipPathClipper->applyClippingToContext(this, targetBoundingBox, repaintRect, context, clipPathClipperContext)) { // 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, clipPathClipperContext); } // Masked content layer start. context->beginLayer(1, CompositeSourceIn, &repaintRect); return true; }
bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* object, const FloatRect& objectBoundingBox, const FloatRect& repaintRect, GraphicsContext* context) { bool missingClipperData = !m_clipper.contains(object); if (missingClipperData) m_clipper.set(object, new ClipperData); bool shouldCreateClipData = false; AffineTransform animatedLocalTransform = static_cast<SVGClipPathElement*>(node())->animatedLocalTransform(); ClipperData* clipperData = m_clipper.get(object); if (!clipperData->clipMaskImage) { if (pathOnlyClipping(context, animatedLocalTransform, objectBoundingBox)) return true; shouldCreateClipData = true; } AffineTransform absoluteTransform; SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(object, absoluteTransform); if (shouldCreateClipData && !repaintRect.isEmpty()) { if (!SVGRenderingContext::createImageBuffer(repaintRect, absoluteTransform, clipperData->clipMaskImage, ColorSpaceDeviceRGB, Unaccelerated)) return false; GraphicsContext* maskContext = clipperData->clipMaskImage->context(); ASSERT(maskContext); maskContext->concatCTM(animatedLocalTransform); // clipPath can also be clipped by another clipPath. SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this); RenderSVGResourceClipper* clipper; bool succeeded; if (resources && (clipper = resources->clipper())) { GraphicsContextStateSaver stateSaver(*maskContext); if (!clipper->applyClippingToContext(this, objectBoundingBox, repaintRect, maskContext)) return false; succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox); // The context restore applies the clipping on non-CG platforms. } else succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox); if (!succeeded) clipperData->clipMaskImage.clear(); } if (!clipperData->clipMaskImage) return false; SVGRenderingContext::clipToImageBuffer(context, absoluteTransform, repaintRect, clipperData->clipMaskImage, missingClipperData); return true; }
bool RenderSVGResourceClipper::applyClippingToContext(RenderElement& renderer, const FloatRect& objectBoundingBox, const FloatRect& repaintRect, GraphicsContext& context) { ClipperMaskImage& clipperMaskImage = addRendererToClipper(renderer); bool shouldCreateClipperMaskImage = !clipperMaskImage; AffineTransform animatedLocalTransform = clipPathElement().animatedLocalTransform(); if (shouldCreateClipperMaskImage && pathOnlyClipping(context, animatedLocalTransform, objectBoundingBox)) return true; AffineTransform absoluteTransform = SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(renderer); if (shouldCreateClipperMaskImage && !repaintRect.isEmpty()) { // FIXME (149469): This image buffer should not be unconditionally unaccelerated. Making it match the context breaks nested clipping, though. clipperMaskImage = SVGRenderingContext::createImageBuffer(repaintRect, absoluteTransform, ColorSpaceSRGB, Unaccelerated); if (!clipperMaskImage) return false; GraphicsContext& maskContext = clipperMaskImage->context(); maskContext.concatCTM(animatedLocalTransform); // clipPath can also be clipped by another clipPath. auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*this); RenderSVGResourceClipper* clipper; bool succeeded; if (resources && (clipper = resources->clipper())) { GraphicsContextStateSaver stateSaver(maskContext); if (!clipper->applyClippingToContext(*this, objectBoundingBox, repaintRect, maskContext)) return false; succeeded = drawContentIntoMaskImage(clipperMaskImage, objectBoundingBox); // The context restore applies the clipping on non-CG platforms. } else succeeded = drawContentIntoMaskImage(clipperMaskImage, objectBoundingBox); if (!succeeded) clipperMaskImage.reset(); } if (!clipperMaskImage) return false; SVGRenderingContext::clipToImageBuffer(context, absoluteTransform, repaintRect, clipperMaskImage, shouldCreateClipperMaskImage); return true; }
void writeSVGResource(TextStream& ts, const RenderObject& object, int indent) { writeStandardPrefix(ts, object, indent); Element* element = static_cast<Element*>(object.node()); const AtomicString& id = element->getIDAttribute(); writeNameAndQuotedValue(ts, "id", id); RenderSVGResource* resource = const_cast<RenderObject&>(object).toRenderSVGResource(); if (resource->resourceType() == MaskerResourceType) { RenderSVGResourceMasker* masker = static_cast<RenderSVGResourceMasker*>(resource); ASSERT(masker); writeNameValuePair(ts, "maskUnits", masker->maskUnits()); writeNameValuePair(ts, "maskContentUnits", masker->maskContentUnits()); } else if (resource->resourceType() == ClipperResourceType) { RenderSVGResourceClipper* clipper = static_cast<RenderSVGResourceClipper*>(resource); ASSERT(clipper); writeNameValuePair(ts, "clipPathUnits", clipper->clipPathUnits()); } // FIXME: Handle other RenderSVGResource* classes here, after converting them from SVGResource*. ts << "\n"; writeChildren(ts, object, indent); }
void SVGRenderingContext::prepareToRenderSVGContent(RenderElement& renderer, PaintInfo& paintInfo, NeedsGraphicsContextSave needsGraphicsContextSave) { #ifndef NDEBUG // This function must not be called twice! ASSERT(!(m_renderingFlags & PrepareToRenderSVGContentWasCalled)); m_renderingFlags |= PrepareToRenderSVGContentWasCalled; #endif m_renderer = &renderer; m_paintInfo = &paintInfo; m_filter = 0; // We need to save / restore the context even if the initialization failed. if (needsGraphicsContextSave == SaveGraphicsContext) { m_paintInfo->context().save(); m_renderingFlags |= RestoreGraphicsContext; } auto& style = m_renderer->style(); const SVGRenderStyle& svgStyle = style.svgStyle(); // Setup transparency layers before setting up SVG resources! bool isRenderingMask = isRenderingMaskImage(*m_renderer); // RenderLayer takes care of root opacity. float opacity = (renderer.isSVGRoot() || isRenderingMask) ? 1 : style.opacity(); const ShadowData* shadow = svgStyle.shadow(); bool hasBlendMode = style.hasBlendMode(); bool hasIsolation = style.hasIsolation(); bool isolateMaskForBlending = false; #if ENABLE(CSS_COMPOSITING) if (svgStyle.hasMasker() && is<SVGGraphicsElement>(downcast<SVGElement>(*renderer.element()))) { SVGGraphicsElement& graphicsElement = downcast<SVGGraphicsElement>(*renderer.element()); isolateMaskForBlending = graphicsElement.shouldIsolateBlending(); } #endif if (opacity < 1 || shadow || hasBlendMode || isolateMaskForBlending || hasIsolation) { FloatRect repaintRect = m_renderer->repaintRectInLocalCoordinates(); m_paintInfo->context().clip(repaintRect); if (opacity < 1 || hasBlendMode || isolateMaskForBlending || hasIsolation) { if (hasBlendMode) m_paintInfo->context().setCompositeOperation(m_paintInfo->context().compositeOperation(), style.blendMode()); m_paintInfo->context().beginTransparencyLayer(opacity); if (hasBlendMode) m_paintInfo->context().setCompositeOperation(m_paintInfo->context().compositeOperation(), BlendModeNormal); m_renderingFlags |= EndOpacityLayer; } if (shadow) { m_paintInfo->context().setShadow(IntSize(roundToInt(shadow->x()), roundToInt(shadow->y())), shadow->radius(), shadow->color()); m_paintInfo->context().beginTransparencyLayer(1); m_renderingFlags |= EndShadowLayer; } } ClipPathOperation* clipPathOperation = style.clipPath(); if (is<ShapeClipPathOperation>(clipPathOperation)) { auto& clipPath = downcast<ShapeClipPathOperation>(*clipPathOperation); FloatRect referenceBox; if (clipPath.referenceBox() == Stroke) // FIXME: strokeBoundingBox() takes dasharray into account but shouldn't. referenceBox = renderer.strokeBoundingBox(); else if (clipPath.referenceBox() == ViewBox && renderer.element()) { FloatSize viewportSize; SVGLengthContext(downcast<SVGElement>(renderer.element())).determineViewport(viewportSize); referenceBox.setWidth(viewportSize.width()); referenceBox.setHeight(viewportSize.height()); } else referenceBox = renderer.objectBoundingBox(); m_paintInfo->context().clipPath(clipPath.pathForReferenceRect(referenceBox), clipPath.windRule()); } auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*m_renderer); if (!resources) { if (style.hasReferenceFilterOnly()) return; m_renderingFlags |= RenderingPrepared; return; } if (!isRenderingMask) { if (RenderSVGResourceMasker* masker = resources->masker()) { GraphicsContext* contextPtr = &m_paintInfo->context(); bool result = masker->applyResource(*m_renderer, style, contextPtr, ApplyToDefaultMode); m_paintInfo->setContext(*contextPtr); if (!result) return; } } RenderSVGResourceClipper* clipper = resources->clipper(); if (!clipPathOperation && clipper) { GraphicsContext* contextPtr = &m_paintInfo->context(); bool result = clipper->applyResource(*m_renderer, style, contextPtr, ApplyToDefaultMode); m_paintInfo->setContext(*contextPtr); if (!result) return; } if (!isRenderingMask) { m_filter = resources->filter(); if (m_filter) { m_savedContext = &m_paintInfo->context(); m_savedPaintRect = m_paintInfo->rect; // Return with false here may mean that we don't need to draw the content // (because it was either drawn before or empty) but we still need to apply the filter. m_renderingFlags |= EndFilterLayer; GraphicsContext* contextPtr = &m_paintInfo->context(); bool result = m_filter->applyResource(*m_renderer, style, contextPtr, ApplyToDefaultMode); m_paintInfo->setContext(*contextPtr); if (!result) return; // Since we're caching the resulting bitmap and do not invalidate it on repaint rect // changes, we need to paint the whole filter region. Otherwise, elements not visible // at the time of the initial paint (due to scrolling, window size, etc.) will never // be drawn. m_paintInfo->rect = IntRect(m_filter->drawingRegion(m_renderer)); } } m_renderingFlags |= RenderingPrepared; }
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; }
void writeSVGResourceContainer(TextStream& ts, const RenderObject& object, int indent) { writeStandardPrefix(ts, object, indent); Element* element = toElement(object.node()); const AtomicString& id = element->getIdAttribute(); writeNameAndQuotedValue(ts, "id", id); RenderSVGResourceContainer* resource = const_cast<RenderObject&>(object).toRenderSVGResourceContainer(); ASSERT(resource); if (resource->resourceType() == MaskerResourceType) { RenderSVGResourceMasker* masker = static_cast<RenderSVGResourceMasker*>(resource); writeNameValuePair(ts, "maskUnits", masker->maskUnits()); writeNameValuePair(ts, "maskContentUnits", masker->maskContentUnits()); ts << "\n"; #if ENABLE(FILTERS) } else if (resource->resourceType() == FilterResourceType) { RenderSVGResourceFilter* filter = static_cast<RenderSVGResourceFilter*>(resource); writeNameValuePair(ts, "filterUnits", filter->filterUnits()); writeNameValuePair(ts, "primitiveUnits", filter->primitiveUnits()); ts << "\n"; // Creating a placeholder filter which is passed to the builder. FloatRect dummyRect; RefPtr<SVGFilter> dummyFilter = SVGFilter::create(AffineTransform(), dummyRect, dummyRect, dummyRect, true); if (RefPtr<SVGFilterBuilder> builder = filter->buildPrimitives(dummyFilter.get())) { if (FilterEffect* lastEffect = builder->lastEffect()) lastEffect->externalRepresentation(ts, indent + 1); } #endif } else if (resource->resourceType() == ClipperResourceType) { RenderSVGResourceClipper* clipper = static_cast<RenderSVGResourceClipper*>(resource); writeNameValuePair(ts, "clipPathUnits", clipper->clipPathUnits()); ts << "\n"; } else if (resource->resourceType() == MarkerResourceType) { RenderSVGResourceMarker* marker = static_cast<RenderSVGResourceMarker*>(resource); writeNameValuePair(ts, "markerUnits", marker->markerUnits()); ts << " [ref at " << marker->referencePoint() << "]"; ts << " [angle="; if (marker->angle() == -1) ts << "auto" << "]\n"; else ts << marker->angle() << "]\n"; } else if (resource->resourceType() == PatternResourceType) { RenderSVGResourcePattern* pattern = static_cast<RenderSVGResourcePattern*>(resource); // Dump final results that are used for rendering. No use in asking SVGPatternElement for its patternUnits(), as it may // link to other patterns using xlink:href, we need to build the full inheritance chain, aka. collectPatternProperties() PatternAttributes attributes; static_cast<SVGPatternElement*>(pattern->node())->collectPatternAttributes(attributes); writeNameValuePair(ts, "patternUnits", attributes.patternUnits()); writeNameValuePair(ts, "patternContentUnits", attributes.patternContentUnits()); AffineTransform transform = attributes.patternTransform(); if (!transform.isIdentity()) ts << " [patternTransform=" << transform << "]"; ts << "\n"; } else if (resource->resourceType() == LinearGradientResourceType) { RenderSVGResourceLinearGradient* gradient = static_cast<RenderSVGResourceLinearGradient*>(resource); // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties() SVGLinearGradientElement* linearGradientElement = static_cast<SVGLinearGradientElement*>(gradient->node()); LinearGradientAttributes attributes; linearGradientElement->collectGradientAttributes(attributes); writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.gradientUnits()); ts << " [start=" << gradient->startPoint(attributes) << "] [end=" << gradient->endPoint(attributes) << "]\n"; } else if (resource->resourceType() == RadialGradientResourceType) { RenderSVGResourceRadialGradient* gradient = static_cast<RenderSVGResourceRadialGradient*>(resource); // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties() SVGRadialGradientElement* radialGradientElement = static_cast<SVGRadialGradientElement*>(gradient->node()); RadialGradientAttributes attributes; radialGradientElement->collectGradientAttributes(attributes); writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.gradientUnits()); FloatPoint focalPoint = gradient->focalPoint(attributes); FloatPoint centerPoint = gradient->centerPoint(attributes); float radius = gradient->radius(attributes); float focalRadius = gradient->focalRadius(attributes); ts << " [center=" << centerPoint << "] [focal=" << focalPoint << "] [radius=" << radius << "] [focalRadius=" << focalRadius << "]\n"; } else ts << "\n"; writeChildren(ts, object, indent); }