| 1 | /* |
| 2 | * Copyright (C) 2007, 2008 Rob Buis <buis@kde.org> |
| 3 | * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> |
| 4 | * Copyright (C) 2007 Eric Seidel <eric@webkit.org> |
| 5 | * Copyright (C) 2009 Google, Inc. All rights reserved. |
| 6 | * Copyright (C) 2009 Dirk Schulze <krit@webkit.org> |
| 7 | * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. |
| 8 | * Copyright (C) 2018 Adobe Systems Incorporated. All rights reserved. |
| 9 | * |
| 10 | * This library is free software; you can redistribute it and/or |
| 11 | * modify it under the terms of the GNU Library General Public |
| 12 | * License as published by the Free Software Foundation; either |
| 13 | * version 2 of the License, or (at your option) any later version. |
| 14 | * |
| 15 | * This library is distributed in the hope that it will be useful, |
| 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 18 | * Library General Public License for more details. |
| 19 | * |
| 20 | * You should have received a copy of the GNU Library General Public License |
| 21 | * along with this library; see the file COPYING.LIB. If not, write to |
| 22 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 23 | * Boston, MA 02110-1301, USA. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "SVGRenderingContext.h" |
| 28 | |
| 29 | #include "BasicShapes.h" |
| 30 | #include "Frame.h" |
| 31 | #include "FrameView.h" |
| 32 | #include "RenderLayer.h" |
| 33 | #include "RenderSVGImage.h" |
| 34 | #include "RenderSVGResourceClipper.h" |
| 35 | #include "RenderSVGResourceFilter.h" |
| 36 | #include "RenderSVGResourceMasker.h" |
| 37 | #include "RenderView.h" |
| 38 | #include "SVGLengthContext.h" |
| 39 | #include "SVGResources.h" |
| 40 | #include "SVGResourcesCache.h" |
| 41 | |
| 42 | namespace WebCore { |
| 43 | |
| 44 | static inline bool isRenderingMaskImage(const RenderObject& object) |
| 45 | { |
| 46 | return object.view().frameView().paintBehavior().contains(PaintBehavior::RenderingSVGMask); |
| 47 | } |
| 48 | |
| 49 | SVGRenderingContext::~SVGRenderingContext() |
| 50 | { |
| 51 | // Fast path if we don't need to restore anything. |
| 52 | if (!(m_renderingFlags & ActionsNeeded)) |
| 53 | return; |
| 54 | |
| 55 | ASSERT(m_renderer && m_paintInfo); |
| 56 | |
| 57 | if (m_renderingFlags & EndFilterLayer) { |
| 58 | ASSERT(m_filter); |
| 59 | GraphicsContext* contextPtr = &m_paintInfo->context(); |
| 60 | m_filter->postApplyResource(*m_renderer, contextPtr, { }, nullptr, nullptr); |
| 61 | m_paintInfo->setContext(*m_savedContext); |
| 62 | m_paintInfo->rect = m_savedPaintRect; |
| 63 | } |
| 64 | |
| 65 | if (m_renderingFlags & EndOpacityLayer) |
| 66 | m_paintInfo->context().endTransparencyLayer(); |
| 67 | |
| 68 | if (m_renderingFlags & EndShadowLayer) |
| 69 | m_paintInfo->context().endTransparencyLayer(); |
| 70 | |
| 71 | if (m_renderingFlags & RestoreGraphicsContext) |
| 72 | m_paintInfo->context().restore(); |
| 73 | } |
| 74 | |
| 75 | void SVGRenderingContext::prepareToRenderSVGContent(RenderElement& renderer, PaintInfo& paintInfo, NeedsGraphicsContextSave needsGraphicsContextSave) |
| 76 | { |
| 77 | #ifndef NDEBUG |
| 78 | // This function must not be called twice! |
| 79 | ASSERT(!(m_renderingFlags & PrepareToRenderSVGContentWasCalled)); |
| 80 | m_renderingFlags |= PrepareToRenderSVGContentWasCalled; |
| 81 | #endif |
| 82 | |
| 83 | m_renderer = &renderer; |
| 84 | m_paintInfo = &paintInfo; |
| 85 | m_filter = nullptr; |
| 86 | |
| 87 | // We need to save / restore the context even if the initialization failed. |
| 88 | if (needsGraphicsContextSave == SaveGraphicsContext) { |
| 89 | m_paintInfo->context().save(); |
| 90 | m_renderingFlags |= RestoreGraphicsContext; |
| 91 | } |
| 92 | |
| 93 | auto& style = m_renderer->style(); |
| 94 | |
| 95 | const SVGRenderStyle& svgStyle = style.svgStyle(); |
| 96 | |
| 97 | // Setup transparency layers before setting up SVG resources! |
| 98 | bool isRenderingMask = isRenderingMaskImage(*m_renderer); |
| 99 | // RenderLayer takes care of root opacity. |
| 100 | float opacity = (renderer.isSVGRoot() || isRenderingMask) ? 1 : style.opacity(); |
| 101 | const ShadowData* shadow = svgStyle.shadow(); |
| 102 | bool hasBlendMode = style.hasBlendMode(); |
| 103 | bool hasIsolation = style.hasIsolation(); |
| 104 | bool isolateMaskForBlending = false; |
| 105 | |
| 106 | #if ENABLE(CSS_COMPOSITING) |
| 107 | if (svgStyle.hasMasker() && is<SVGGraphicsElement>(downcast<SVGElement>(*renderer.element()))) { |
| 108 | SVGGraphicsElement& graphicsElement = downcast<SVGGraphicsElement>(*renderer.element()); |
| 109 | isolateMaskForBlending = graphicsElement.shouldIsolateBlending(); |
| 110 | } |
| 111 | #endif |
| 112 | |
| 113 | if (opacity < 1 || shadow || hasBlendMode || isolateMaskForBlending || hasIsolation) { |
| 114 | FloatRect repaintRect = m_renderer->repaintRectInLocalCoordinates(); |
| 115 | m_paintInfo->context().clip(repaintRect); |
| 116 | |
| 117 | if (opacity < 1 || hasBlendMode || isolateMaskForBlending || hasIsolation) { |
| 118 | |
| 119 | if (hasBlendMode) |
| 120 | m_paintInfo->context().setCompositeOperation(m_paintInfo->context().compositeOperation(), style.blendMode()); |
| 121 | |
| 122 | m_paintInfo->context().beginTransparencyLayer(opacity); |
| 123 | |
| 124 | if (hasBlendMode) |
| 125 | m_paintInfo->context().setCompositeOperation(m_paintInfo->context().compositeOperation(), BlendMode::Normal); |
| 126 | |
| 127 | m_renderingFlags |= EndOpacityLayer; |
| 128 | } |
| 129 | |
| 130 | if (shadow) { |
| 131 | m_paintInfo->context().setShadow(IntSize(roundToInt(shadow->x()), roundToInt(shadow->y())), shadow->radius(), shadow->color()); |
| 132 | m_paintInfo->context().beginTransparencyLayer(1); |
| 133 | m_renderingFlags |= EndShadowLayer; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | ClipPathOperation* clipPathOperation = style.clipPath(); |
| 138 | bool hasCSSClipping = is<ShapeClipPathOperation>(clipPathOperation) || is<BoxClipPathOperation>(clipPathOperation); |
| 139 | if (hasCSSClipping) |
| 140 | SVGRenderSupport::clipContextToCSSClippingArea(m_paintInfo->context(), renderer); |
| 141 | |
| 142 | auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*m_renderer); |
| 143 | if (!resources) { |
| 144 | if (style.hasReferenceFilterOnly()) |
| 145 | return; |
| 146 | |
| 147 | m_renderingFlags |= RenderingPrepared; |
| 148 | return; |
| 149 | } |
| 150 | |
| 151 | if (!isRenderingMask) { |
| 152 | if (RenderSVGResourceMasker* masker = resources->masker()) { |
| 153 | GraphicsContext* contextPtr = &m_paintInfo->context(); |
| 154 | bool result = masker->applyResource(*m_renderer, style, contextPtr, { }); |
| 155 | m_paintInfo->setContext(*contextPtr); |
| 156 | if (!result) |
| 157 | return; |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | RenderSVGResourceClipper* clipper = resources->clipper(); |
| 162 | if (!hasCSSClipping && clipper) { |
| 163 | GraphicsContext* contextPtr = &m_paintInfo->context(); |
| 164 | bool result = clipper->applyResource(*m_renderer, style, contextPtr, { }); |
| 165 | m_paintInfo->setContext(*contextPtr); |
| 166 | if (!result) |
| 167 | return; |
| 168 | } |
| 169 | |
| 170 | if (!isRenderingMask) { |
| 171 | m_filter = resources->filter(); |
| 172 | if (m_filter) { |
| 173 | m_savedContext = &m_paintInfo->context(); |
| 174 | m_savedPaintRect = m_paintInfo->rect; |
| 175 | // Return with false here may mean that we don't need to draw the content |
| 176 | // (because it was either drawn before or empty) but we still need to apply the filter. |
| 177 | m_renderingFlags |= EndFilterLayer; |
| 178 | GraphicsContext* contextPtr = &m_paintInfo->context(); |
| 179 | bool result = m_filter->applyResource(*m_renderer, style, contextPtr, { }); |
| 180 | m_paintInfo->setContext(*contextPtr); |
| 181 | if (!result) |
| 182 | return; |
| 183 | |
| 184 | // Since we're caching the resulting bitmap and do not invalidate it on repaint rect |
| 185 | // changes, we need to paint the whole filter region. Otherwise, elements not visible |
| 186 | // at the time of the initial paint (due to scrolling, window size, etc.) will never |
| 187 | // be drawn. |
| 188 | m_paintInfo->rect = IntRect(m_filter->drawingRegion(m_renderer)); |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | m_renderingFlags |= RenderingPrepared; |
| 193 | } |
| 194 | |
| 195 | static AffineTransform& currentContentTransformation() |
| 196 | { |
| 197 | static NeverDestroyed<AffineTransform> s_currentContentTransformation; |
| 198 | return s_currentContentTransformation; |
| 199 | } |
| 200 | |
| 201 | float SVGRenderingContext::calculateScreenFontSizeScalingFactor(const RenderObject& renderer) |
| 202 | { |
| 203 | AffineTransform ctm = calculateTransformationToOutermostCoordinateSystem(renderer); |
| 204 | return narrowPrecisionToFloat(sqrt((pow(ctm.xScale(), 2) + pow(ctm.yScale(), 2)) / 2)); |
| 205 | } |
| 206 | |
| 207 | AffineTransform SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(const RenderObject& renderer) |
| 208 | { |
| 209 | AffineTransform absoluteTransform = currentContentTransformation(); |
| 210 | |
| 211 | float deviceScaleFactor = renderer.document().deviceScaleFactor(); |
| 212 | // Walk up the render tree, accumulating SVG transforms. |
| 213 | const RenderObject* ancestor = &renderer; |
| 214 | while (ancestor) { |
| 215 | absoluteTransform = ancestor->localToParentTransform() * absoluteTransform; |
| 216 | if (ancestor->isSVGRoot()) |
| 217 | break; |
| 218 | ancestor = ancestor->parent(); |
| 219 | } |
| 220 | |
| 221 | // Continue walking up the layer tree, accumulating CSS transforms. |
| 222 | RenderLayer* layer = ancestor ? ancestor->enclosingLayer() : nullptr; |
| 223 | while (layer) { |
| 224 | if (TransformationMatrix* layerTransform = layer->transform()) |
| 225 | absoluteTransform = layerTransform->toAffineTransform() * absoluteTransform; |
| 226 | |
| 227 | // We can stop at compositing layers, to match the backing resolution. |
| 228 | if (layer->isComposited()) |
| 229 | break; |
| 230 | |
| 231 | layer = layer->parent(); |
| 232 | } |
| 233 | |
| 234 | absoluteTransform.scale(deviceScaleFactor); |
| 235 | return absoluteTransform; |
| 236 | } |
| 237 | |
| 238 | std::unique_ptr<ImageBuffer> SVGRenderingContext::createImageBuffer(const FloatRect& targetRect, const AffineTransform& absoluteTransform, ColorSpace colorSpace, RenderingMode renderingMode, const GraphicsContext* context) |
| 239 | { |
| 240 | IntRect paintRect = calculateImageBufferRect(targetRect, absoluteTransform); |
| 241 | // Don't create empty ImageBuffers. |
| 242 | if (paintRect.isEmpty()) |
| 243 | return nullptr; |
| 244 | |
| 245 | FloatSize scale; |
| 246 | FloatSize clampedSize = ImageBuffer::clampedSize(paintRect.size(), scale); |
| 247 | |
| 248 | #if USE(DIRECT2D) |
| 249 | auto imageBuffer = ImageBuffer::create(clampedSize, renderingMode, context, 1, colorSpace); |
| 250 | #else |
| 251 | UNUSED_PARAM(context); |
| 252 | auto imageBuffer = ImageBuffer::create(clampedSize, renderingMode, 1, colorSpace); |
| 253 | #endif |
| 254 | if (!imageBuffer) |
| 255 | return nullptr; |
| 256 | |
| 257 | AffineTransform transform; |
| 258 | transform.scale(scale).translate(-paintRect.location()).multiply(absoluteTransform); |
| 259 | |
| 260 | GraphicsContext& imageContext = imageBuffer->context(); |
| 261 | imageContext.concatCTM(transform); |
| 262 | |
| 263 | return imageBuffer; |
| 264 | } |
| 265 | |
| 266 | std::unique_ptr<ImageBuffer> SVGRenderingContext::createImageBuffer(const FloatRect& targetRect, const FloatRect& clampedRect, ColorSpace colorSpace, RenderingMode renderingMode, const GraphicsContext* context) |
| 267 | { |
| 268 | IntSize clampedSize = roundedIntSize(clampedRect.size()); |
| 269 | FloatSize unclampedSize = roundedIntSize(targetRect.size()); |
| 270 | |
| 271 | // Don't create empty ImageBuffers. |
| 272 | if (clampedSize.isEmpty()) |
| 273 | return nullptr; |
| 274 | |
| 275 | #if USE(DIRECT2D) |
| 276 | auto imageBuffer = ImageBuffer::create(clampedSize, renderingMode, context, 1, colorSpace); |
| 277 | #else |
| 278 | UNUSED_PARAM(context); |
| 279 | auto imageBuffer = ImageBuffer::create(clampedSize, renderingMode, 1, colorSpace); |
| 280 | #endif |
| 281 | if (!imageBuffer) |
| 282 | return nullptr; |
| 283 | |
| 284 | GraphicsContext& imageContext = imageBuffer->context(); |
| 285 | |
| 286 | // Compensate rounding effects, as the absolute target rect is using floating-point numbers and the image buffer size is integer. |
| 287 | imageContext.scale(unclampedSize / targetRect.size()); |
| 288 | |
| 289 | return imageBuffer; |
| 290 | } |
| 291 | |
| 292 | void SVGRenderingContext::renderSubtreeToImageBuffer(ImageBuffer* image, RenderElement& item, const AffineTransform& subtreeContentTransformation) |
| 293 | { |
| 294 | ASSERT(image); |
| 295 | |
| 296 | // Rendering into a buffer implies we're being used for masking, clipping, patterns or filters. In each of these |
| 297 | // cases we don't want to paint the selection. |
| 298 | PaintInfo info(image->context(), LayoutRect::infiniteRect(), PaintPhase::Foreground, PaintBehavior::SkipSelectionHighlight); |
| 299 | |
| 300 | AffineTransform& contentTransformation = currentContentTransformation(); |
| 301 | AffineTransform savedContentTransformation = contentTransformation; |
| 302 | contentTransformation = subtreeContentTransformation * contentTransformation; |
| 303 | |
| 304 | ASSERT(!item.needsLayout()); |
| 305 | item.paint(info, { }); |
| 306 | |
| 307 | contentTransformation = savedContentTransformation; |
| 308 | } |
| 309 | |
| 310 | void SVGRenderingContext::clipToImageBuffer(GraphicsContext& context, const AffineTransform& absoluteTransform, const FloatRect& targetRect, std::unique_ptr<ImageBuffer>& imageBuffer, bool safeToClear) |
| 311 | { |
| 312 | if (!imageBuffer) |
| 313 | return; |
| 314 | |
| 315 | FloatRect absoluteTargetRect = calculateImageBufferRect(targetRect, absoluteTransform); |
| 316 | |
| 317 | // The mask image has been created in the absolute coordinate space, as the image should not be scaled. |
| 318 | // So the actual masking process has to be done in the absolute coordinate space as well. |
| 319 | context.concatCTM(absoluteTransform.inverse().valueOr(AffineTransform())); |
| 320 | context.clipToImageBuffer(*imageBuffer, absoluteTargetRect); |
| 321 | context.concatCTM(absoluteTransform); |
| 322 | |
| 323 | // When nesting resources, with objectBoundingBox as content unit types, there's no use in caching the |
| 324 | // resulting image buffer as the parent resource already caches the result. |
| 325 | if (safeToClear && !currentContentTransformation().isIdentity()) |
| 326 | imageBuffer.reset(); |
| 327 | } |
| 328 | |
| 329 | void SVGRenderingContext::clear2DRotation(AffineTransform& transform) |
| 330 | { |
| 331 | AffineTransform::DecomposedType decomposition; |
| 332 | transform.decompose(decomposition); |
| 333 | decomposition.angle = 0; |
| 334 | transform.recompose(decomposition); |
| 335 | } |
| 336 | |
| 337 | bool SVGRenderingContext::bufferForeground(std::unique_ptr<ImageBuffer>& imageBuffer) |
| 338 | { |
| 339 | ASSERT(m_paintInfo); |
| 340 | ASSERT(is<RenderSVGImage>(*m_renderer)); |
| 341 | FloatRect boundingBox = m_renderer->objectBoundingBox(); |
| 342 | |
| 343 | // Invalidate an existing buffer if the scale is not correct. |
| 344 | if (imageBuffer) { |
| 345 | AffineTransform transform = m_paintInfo->context().getCTM(GraphicsContext::DefinitelyIncludeDeviceScale); |
| 346 | IntSize expandedBoundingBox = expandedIntSize(boundingBox.size()); |
| 347 | IntSize bufferSize(static_cast<int>(ceil(expandedBoundingBox.width() * transform.xScale())), static_cast<int>(ceil(expandedBoundingBox.height() * transform.yScale()))); |
| 348 | if (bufferSize != imageBuffer->internalSize()) |
| 349 | imageBuffer.reset(); |
| 350 | } |
| 351 | |
| 352 | // Create a new buffer and paint the foreground into it. |
| 353 | if (!imageBuffer) { |
| 354 | if ((imageBuffer = ImageBuffer::createCompatibleBuffer(expandedIntSize(boundingBox.size()), ColorSpaceSRGB, m_paintInfo->context()))) { |
| 355 | GraphicsContext& bufferedRenderingContext = imageBuffer->context(); |
| 356 | bufferedRenderingContext.translate(-boundingBox.location()); |
| 357 | PaintInfo bufferedInfo(*m_paintInfo); |
| 358 | bufferedInfo.setContext(bufferedRenderingContext); |
| 359 | downcast<RenderSVGImage>(*m_renderer).paintForeground(bufferedInfo); |
| 360 | } else |
| 361 | return false; |
| 362 | } |
| 363 | |
| 364 | m_paintInfo->context().drawImageBuffer(*imageBuffer, boundingBox); |
| 365 | return true; |
| 366 | } |
| 367 | |
| 368 | } |
| 369 | |