| 1 | /* |
| 2 | * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> |
| 3 | * Copyright (C) 2008 Eric Seidel <eric@webkit.org> |
| 4 | * Copyright (C) 2008 Dirk Schulze <krit@webkit.org> |
| 5 | * Copyright (C) Research In Motion Limited 2010. All rights reserved. |
| 6 | * |
| 7 | * This library is free software; you can redistribute it and/or |
| 8 | * modify it under the terms of the GNU Library General Public |
| 9 | * License as published by the Free Software Foundation; either |
| 10 | * version 2 of the License, or (at your option) any later version. |
| 11 | * |
| 12 | * This library is distributed in the hope that it will be useful, |
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 | * Library General Public License for more details. |
| 16 | * |
| 17 | * You should have received a copy of the GNU Library General Public License |
| 18 | * along with this library; see the file COPYING.LIB. If not, write to |
| 19 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 20 | * Boston, MA 02110-1301, USA. |
| 21 | */ |
| 22 | |
| 23 | #include "config.h" |
| 24 | #include "RenderSVGResourceGradient.h" |
| 25 | |
| 26 | #include "GradientAttributes.h" |
| 27 | #include "GraphicsContext.h" |
| 28 | #include "RenderSVGText.h" |
| 29 | #include "SVGRenderingContext.h" |
| 30 | #include <wtf/IsoMallocInlines.h> |
| 31 | |
| 32 | namespace WebCore { |
| 33 | |
| 34 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGResourceGradient); |
| 35 | |
| 36 | RenderSVGResourceGradient::RenderSVGResourceGradient(SVGGradientElement& node, RenderStyle&& style) |
| 37 | : RenderSVGResourceContainer(node, WTFMove(style)) |
| 38 | { |
| 39 | } |
| 40 | |
| 41 | void RenderSVGResourceGradient::removeAllClientsFromCache(bool markForInvalidation) |
| 42 | { |
| 43 | m_gradientMap.clear(); |
| 44 | m_shouldCollectGradientAttributes = true; |
| 45 | markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); |
| 46 | } |
| 47 | |
| 48 | void RenderSVGResourceGradient::removeClientFromCache(RenderElement& client, bool markForInvalidation) |
| 49 | { |
| 50 | m_gradientMap.remove(&client); |
| 51 | markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); |
| 52 | } |
| 53 | |
| 54 | #if USE(CG) |
| 55 | static inline bool createMaskAndSwapContextForTextGradient(GraphicsContext*& context, GraphicsContext*& savedContext, std::unique_ptr<ImageBuffer>& imageBuffer, RenderObject* object) |
| 56 | { |
| 57 | auto* textRootBlock = RenderSVGText::locateRenderSVGTextAncestor(*object); |
| 58 | ASSERT(textRootBlock); |
| 59 | |
| 60 | AffineTransform absoluteTransform = SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(*textRootBlock); |
| 61 | FloatRect repaintRect = textRootBlock->repaintRectInLocalCoordinates(); |
| 62 | |
| 63 | auto maskImage = SVGRenderingContext::createImageBuffer(repaintRect, absoluteTransform, ColorSpaceSRGB, context->renderingMode()); |
| 64 | if (!maskImage) |
| 65 | return false; |
| 66 | |
| 67 | GraphicsContext& maskImageContext = maskImage->context(); |
| 68 | ASSERT(maskImage); |
| 69 | savedContext = context; |
| 70 | context = &maskImageContext; |
| 71 | imageBuffer = WTFMove(maskImage); |
| 72 | return true; |
| 73 | } |
| 74 | |
| 75 | static inline AffineTransform clipToTextMask(GraphicsContext& context, std::unique_ptr<ImageBuffer>& imageBuffer, FloatRect& targetRect, RenderObject* object, bool boundingBoxMode, const AffineTransform& gradientTransform) |
| 76 | { |
| 77 | auto* textRootBlock = RenderSVGText::locateRenderSVGTextAncestor(*object); |
| 78 | ASSERT(textRootBlock); |
| 79 | |
| 80 | AffineTransform absoluteTransform = SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(*textRootBlock); |
| 81 | |
| 82 | targetRect = textRootBlock->repaintRectInLocalCoordinates(); |
| 83 | |
| 84 | SVGRenderingContext::clipToImageBuffer(context, absoluteTransform, targetRect, imageBuffer, false); |
| 85 | |
| 86 | AffineTransform matrix; |
| 87 | if (boundingBoxMode) { |
| 88 | FloatRect maskBoundingBox = textRootBlock->objectBoundingBox(); |
| 89 | matrix.translate(maskBoundingBox.location()); |
| 90 | matrix.scale(maskBoundingBox.size()); |
| 91 | } |
| 92 | matrix *= gradientTransform; |
| 93 | return matrix; |
| 94 | } |
| 95 | #endif |
| 96 | |
| 97 | bool RenderSVGResourceGradient::applyResource(RenderElement& renderer, const RenderStyle& style, GraphicsContext*& context, OptionSet<RenderSVGResourceMode> resourceMode) |
| 98 | { |
| 99 | ASSERT(context); |
| 100 | ASSERT(!resourceMode.isEmpty()); |
| 101 | |
| 102 | // Be sure to synchronize all SVG properties on the gradientElement _before_ processing any further. |
| 103 | // Otherwhise the call to collectGradientAttributes() in createTileImage(), may cause the SVG DOM property |
| 104 | // synchronization to kick in, which causes removeAllClientsFromCache() to be called, which in turn deletes our |
| 105 | // GradientData object! Leaving out the line below will cause svg/dynamic-updates/SVG*GradientElement-svgdom* to crash. |
| 106 | if (m_shouldCollectGradientAttributes) { |
| 107 | gradientElement().synchronizeAllAttributes(); |
| 108 | if (!collectGradientAttributes()) |
| 109 | return false; |
| 110 | |
| 111 | m_shouldCollectGradientAttributes = false; |
| 112 | } |
| 113 | |
| 114 | // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified, |
| 115 | // then the given effect (e.g. a gradient or a filter) will be ignored. |
| 116 | FloatRect objectBoundingBox = renderer.objectBoundingBox(); |
| 117 | if (gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && objectBoundingBox.isEmpty()) |
| 118 | return false; |
| 119 | |
| 120 | auto& gradientData = m_gradientMap.add(&renderer, nullptr).iterator->value; |
| 121 | if (!gradientData) |
| 122 | gradientData = std::make_unique<GradientData>(); |
| 123 | |
| 124 | bool isPaintingText = resourceMode.contains(RenderSVGResourceMode::ApplyToText); |
| 125 | |
| 126 | // Create gradient object |
| 127 | if (!gradientData->gradient) { |
| 128 | buildGradient(gradientData.get(), style); |
| 129 | |
| 130 | // CG platforms will handle the gradient space transform for text after applying the |
| 131 | // resource, so don't apply it here. For non-CG platforms, we want the text bounding |
| 132 | // box applied to the gradient space transform now, so the gradient shader can use it. |
| 133 | #if USE(CG) |
| 134 | if (gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && !objectBoundingBox.isEmpty() && !isPaintingText) { |
| 135 | #else |
| 136 | if (gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && !objectBoundingBox.isEmpty()) { |
| 137 | #endif |
| 138 | gradientData->userspaceTransform.translate(objectBoundingBox.location()); |
| 139 | gradientData->userspaceTransform.scale(objectBoundingBox.size()); |
| 140 | } |
| 141 | |
| 142 | AffineTransform gradientTransform; |
| 143 | calculateGradientTransform(gradientTransform); |
| 144 | |
| 145 | gradientData->userspaceTransform *= gradientTransform; |
| 146 | if (isPaintingText) { |
| 147 | // Depending on font scaling factor, we may need to rescale the gradient here since |
| 148 | // text painting removes the scale factor from the context. |
| 149 | AffineTransform additionalTextTransform; |
| 150 | if (shouldTransformOnTextPainting(renderer, additionalTextTransform)) |
| 151 | gradientData->userspaceTransform *= additionalTextTransform; |
| 152 | } |
| 153 | gradientData->gradient->setGradientSpaceTransform(gradientData->userspaceTransform); |
| 154 | } |
| 155 | |
| 156 | if (!gradientData->gradient) |
| 157 | return false; |
| 158 | |
| 159 | // Draw gradient |
| 160 | context->save(); |
| 161 | |
| 162 | if (isPaintingText) { |
| 163 | #if USE(CG) |
| 164 | if (!createMaskAndSwapContextForTextGradient(context, m_savedContext, m_imageBuffer, &renderer)) { |
| 165 | context->restore(); |
| 166 | return false; |
| 167 | } |
| 168 | #endif |
| 169 | |
| 170 | context->setTextDrawingMode(resourceMode.contains(RenderSVGResourceMode::ApplyToFill) ? TextModeFill : TextModeStroke); |
| 171 | } |
| 172 | |
| 173 | const SVGRenderStyle& svgStyle = style.svgStyle(); |
| 174 | |
| 175 | if (resourceMode.contains(RenderSVGResourceMode::ApplyToFill)) { |
| 176 | context->setAlpha(svgStyle.fillOpacity()); |
| 177 | context->setFillGradient(*gradientData->gradient); |
| 178 | context->setFillRule(svgStyle.fillRule()); |
| 179 | } else if (resourceMode.contains(RenderSVGResourceMode::ApplyToStroke)) { |
| 180 | if (svgStyle.vectorEffect() == VectorEffect::NonScalingStroke) |
| 181 | gradientData->gradient->setGradientSpaceTransform(transformOnNonScalingStroke(&renderer, gradientData->userspaceTransform)); |
| 182 | context->setAlpha(svgStyle.strokeOpacity()); |
| 183 | context->setStrokeGradient(*gradientData->gradient); |
| 184 | SVGRenderSupport::applyStrokeStyleToContext(context, style, renderer); |
| 185 | } |
| 186 | |
| 187 | return true; |
| 188 | } |
| 189 | |
| 190 | void RenderSVGResourceGradient::postApplyResource(RenderElement& renderer, GraphicsContext*& context, OptionSet<RenderSVGResourceMode> resourceMode, const Path* path, const RenderSVGShape* shape) |
| 191 | { |
| 192 | ASSERT(context); |
| 193 | ASSERT(!resourceMode.isEmpty()); |
| 194 | |
| 195 | if (resourceMode.contains(RenderSVGResourceMode::ApplyToText)) { |
| 196 | #if USE(CG) |
| 197 | // CG requires special handling for gradient on text |
| 198 | GradientData* gradientData; |
| 199 | if (m_savedContext && (gradientData = m_gradientMap.get(&renderer))) { |
| 200 | // Restore on-screen drawing context |
| 201 | context = m_savedContext; |
| 202 | m_savedContext = nullptr; |
| 203 | |
| 204 | AffineTransform gradientTransform; |
| 205 | calculateGradientTransform(gradientTransform); |
| 206 | |
| 207 | FloatRect targetRect; |
| 208 | gradientData->gradient->setGradientSpaceTransform(clipToTextMask(*context, m_imageBuffer, targetRect, &renderer, gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, gradientTransform)); |
| 209 | context->setFillGradient(*gradientData->gradient); |
| 210 | |
| 211 | context->fillRect(targetRect); |
| 212 | m_imageBuffer.reset(); |
| 213 | } |
| 214 | #else |
| 215 | UNUSED_PARAM(renderer); |
| 216 | #endif |
| 217 | } else { |
| 218 | if (resourceMode.contains(RenderSVGResourceMode::ApplyToFill)) { |
| 219 | if (path) |
| 220 | context->fillPath(*path); |
| 221 | else if (shape) |
| 222 | shape->fillShape(*context); |
| 223 | } |
| 224 | if (resourceMode.contains(RenderSVGResourceMode::ApplyToStroke)) { |
| 225 | if (path) |
| 226 | context->strokePath(*path); |
| 227 | else if (shape) |
| 228 | shape->strokeShape(*context); |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | context->restore(); |
| 233 | } |
| 234 | |
| 235 | void RenderSVGResourceGradient::addStops(GradientData* gradientData, const Vector<Gradient::ColorStop>& stops, const RenderStyle& style) const |
| 236 | { |
| 237 | ASSERT(gradientData->gradient); |
| 238 | |
| 239 | for (Gradient::ColorStop stop : stops) { |
| 240 | stop.color = style.colorByApplyingColorFilter(stop.color); |
| 241 | gradientData->gradient->addColorStop(stop); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | GradientSpreadMethod RenderSVGResourceGradient::platformSpreadMethodFromSVGType(SVGSpreadMethodType method) const |
| 246 | { |
| 247 | switch (method) { |
| 248 | case SVGSpreadMethodUnknown: |
| 249 | case SVGSpreadMethodPad: |
| 250 | return SpreadMethodPad; |
| 251 | case SVGSpreadMethodReflect: |
| 252 | return SpreadMethodReflect; |
| 253 | case SVGSpreadMethodRepeat: |
| 254 | return SpreadMethodRepeat; |
| 255 | } |
| 256 | |
| 257 | ASSERT_NOT_REACHED(); |
| 258 | return SpreadMethodPad; |
| 259 | } |
| 260 | |
| 261 | } |
| 262 | |