1/*
2 * Copyright (C) 2006 Alexander Kellett <lypanov@kde.org>
3 * Copyright (C) 2006 Apple Inc.
4 * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
5 * Copyright (C) 2007, 2008, 2009 Rob Buis <buis@kde.org>
6 * Copyright (C) 2009 Google, Inc.
7 * Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
8 * Copyright (C) 2010 Patrick Gansterer <paroga@paroga.com>
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 "RenderSVGImage.h"
28
29#include "FloatQuad.h"
30#include "GraphicsContext.h"
31#include "HitTestResult.h"
32#include "LayoutRepainter.h"
33#include "PointerEventsHitRules.h"
34#include "RenderImageResource.h"
35#include "RenderLayer.h"
36#include "RenderSVGResource.h"
37#include "RenderSVGResourceFilter.h"
38#include "SVGImageElement.h"
39#include "SVGRenderingContext.h"
40#include "SVGResources.h"
41#include "SVGResourcesCache.h"
42#include <wtf/IsoMallocInlines.h>
43#include <wtf/StackStats.h>
44
45namespace WebCore {
46
47WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGImage);
48
49RenderSVGImage::RenderSVGImage(SVGImageElement& element, RenderStyle&& style)
50 : RenderSVGModelObject(element, WTFMove(style))
51 , m_needsBoundariesUpdate(true)
52 , m_needsTransformUpdate(true)
53 , m_imageResource(std::make_unique<RenderImageResource>())
54{
55 imageResource().initialize(*this);
56}
57
58RenderSVGImage::~RenderSVGImage() = default;
59
60void RenderSVGImage::willBeDestroyed()
61{
62 imageResource().shutdown();
63 RenderSVGModelObject::willBeDestroyed();
64}
65
66SVGImageElement& RenderSVGImage::imageElement() const
67{
68 return downcast<SVGImageElement>(RenderSVGModelObject::element());
69}
70
71bool RenderSVGImage::updateImageViewport()
72{
73 FloatRect oldBoundaries = m_objectBoundingBox;
74 bool updatedViewport = false;
75
76 SVGLengthContext lengthContext(&imageElement());
77 m_objectBoundingBox = FloatRect(imageElement().x().value(lengthContext), imageElement().y().value(lengthContext), imageElement().width().value(lengthContext), imageElement().height().value(lengthContext));
78
79 URL imageSourceURL = document().completeURL(imageElement().imageSourceURL());
80
81 // Images with preserveAspectRatio=none should force non-uniform scaling. This can be achieved
82 // by setting the image's container size to its intrinsic size.
83 // See: http://www.w3.org/TR/SVG/single-page.html, 7.8 The ‘preserveAspectRatio’ attribute.
84 if (imageElement().preserveAspectRatio().align() == SVGPreserveAspectRatioValue::SVG_PRESERVEASPECTRATIO_NONE) {
85 if (CachedImage* cachedImage = imageResource().cachedImage()) {
86 LayoutSize intrinsicSize = cachedImage->imageSizeForRenderer(nullptr, style().effectiveZoom());
87 if (intrinsicSize != imageResource().imageSize(style().effectiveZoom())) {
88 imageResource().setContainerContext(roundedIntSize(intrinsicSize), imageSourceURL);
89 updatedViewport = true;
90 }
91 }
92 }
93
94 if (oldBoundaries != m_objectBoundingBox) {
95 if (!updatedViewport)
96 imageResource().setContainerContext(enclosingIntRect(m_objectBoundingBox).size(), imageSourceURL);
97 updatedViewport = true;
98 m_needsBoundariesUpdate = true;
99 }
100
101 return updatedViewport;
102}
103
104void RenderSVGImage::layout()
105{
106 StackStats::LayoutCheckPoint layoutCheckPoint;
107 ASSERT(needsLayout());
108
109 LayoutRepainter repainter(*this, SVGRenderSupport::checkForSVGRepaintDuringLayout(*this) && selfNeedsLayout());
110 updateImageViewport();
111
112 bool transformOrBoundariesUpdate = m_needsTransformUpdate || m_needsBoundariesUpdate;
113 if (m_needsTransformUpdate) {
114 m_localTransform = imageElement().animatedLocalTransform();
115 m_needsTransformUpdate = false;
116 }
117
118 if (m_needsBoundariesUpdate) {
119 m_repaintBoundingBoxExcludingShadow = m_objectBoundingBox;
120 SVGRenderSupport::intersectRepaintRectWithResources(*this, m_repaintBoundingBoxExcludingShadow);
121
122 m_repaintBoundingBox = m_repaintBoundingBoxExcludingShadow;
123
124 m_needsBoundariesUpdate = false;
125 }
126
127 // Invalidate all resources of this client if our layout changed.
128 if (everHadLayout() && selfNeedsLayout())
129 SVGResourcesCache::clientLayoutChanged(*this);
130
131 // If our bounds changed, notify the parents.
132 if (transformOrBoundariesUpdate)
133 RenderSVGModelObject::setNeedsBoundariesUpdate();
134
135 repainter.repaintAfterLayout();
136 clearNeedsLayout();
137}
138
139void RenderSVGImage::paint(PaintInfo& paintInfo, const LayoutPoint&)
140{
141 if (paintInfo.context().paintingDisabled() || paintInfo.phase != PaintPhase::Foreground
142 || style().visibility() == Visibility::Hidden || !imageResource().cachedImage())
143 return;
144
145 FloatRect boundingBox = repaintRectInLocalCoordinates();
146 if (!SVGRenderSupport::paintInfoIntersectsRepaintRect(boundingBox, m_localTransform, paintInfo))
147 return;
148
149 PaintInfo childPaintInfo(paintInfo);
150 GraphicsContextStateSaver stateSaver(childPaintInfo.context());
151 childPaintInfo.applyTransform(m_localTransform);
152
153 if (childPaintInfo.phase == PaintPhase::Foreground) {
154 SVGRenderingContext renderingContext(*this, childPaintInfo);
155
156 if (renderingContext.isRenderingPrepared()) {
157 if (style().svgStyle().bufferedRendering() == BufferedRendering::Static && renderingContext.bufferForeground(m_bufferedForeground))
158 return;
159
160 paintForeground(childPaintInfo);
161 }
162 }
163
164 if (style().outlineWidth())
165 paintOutline(childPaintInfo, IntRect(boundingBox));
166}
167
168void RenderSVGImage::paintForeground(PaintInfo& paintInfo)
169{
170 RefPtr<Image> image = imageResource().image();
171 if (!image)
172 return;
173
174 FloatRect destRect = m_objectBoundingBox;
175 FloatRect srcRect(0, 0, image->width(), image->height());
176
177 imageElement().preserveAspectRatio().transformRect(destRect, srcRect);
178
179 paintInfo.context().drawImage(*image, destRect, srcRect);
180}
181
182void RenderSVGImage::invalidateBufferedForeground()
183{
184 m_bufferedForeground.reset();
185}
186
187bool RenderSVGImage::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction)
188{
189 // We only draw in the forground phase, so we only hit-test then.
190 if (hitTestAction != HitTestForeground)
191 return false;
192
193 PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_IMAGE_HITTESTING, request, style().pointerEvents());
194 bool isVisible = (style().visibility() == Visibility::Visible);
195 if (isVisible || !hitRules.requireVisible) {
196 FloatPoint localPoint = localToParentTransform().inverse().valueOr(AffineTransform()).mapPoint(pointInParent);
197
198 if (!SVGRenderSupport::pointInClippingArea(*this, localPoint))
199 return false;
200
201 if (hitRules.canHitFill) {
202 if (m_objectBoundingBox.contains(localPoint)) {
203 updateHitTestResult(result, LayoutPoint(localPoint));
204 if (result.addNodeToListBasedTestResult(&imageElement(), request, localPoint) == HitTestProgress::Stop)
205 return true;
206 }
207 }
208 }
209
210 return false;
211}
212
213void RenderSVGImage::imageChanged(WrappedImagePtr, const IntRect*)
214{
215 // The image resource defaults to nullImage until the resource arrives.
216 // This empty image may be cached by SVG resources which must be invalidated.
217 if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*this))
218 resources->removeClientFromCache(*this);
219
220 // Eventually notify parent resources, that we've changed.
221 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*this, false);
222
223 // Update the SVGImageCache sizeAndScales entry in case image loading finished after layout.
224 // (https://bugs.webkit.org/show_bug.cgi?id=99489)
225 m_objectBoundingBox = FloatRect();
226 updateImageViewport();
227
228 invalidateBufferedForeground();
229
230 repaint();
231}
232
233void RenderSVGImage::addFocusRingRects(Vector<LayoutRect>& rects, const LayoutPoint&, const RenderLayerModelObject*)
234{
235 // this is called from paint() after the localTransform has already been applied
236 LayoutRect contentRect = LayoutRect(repaintRectInLocalCoordinates());
237 if (!contentRect.isEmpty())
238 rects.append(contentRect);
239}
240
241} // namespace WebCore
242