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 | |
45 | namespace WebCore { |
46 | |
47 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGImage); |
48 | |
49 | RenderSVGImage::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 | |
58 | RenderSVGImage::~RenderSVGImage() = default; |
59 | |
60 | void RenderSVGImage::willBeDestroyed() |
61 | { |
62 | imageResource().shutdown(); |
63 | RenderSVGModelObject::willBeDestroyed(); |
64 | } |
65 | |
66 | SVGImageElement& RenderSVGImage::imageElement() const |
67 | { |
68 | return downcast<SVGImageElement>(RenderSVGModelObject::element()); |
69 | } |
70 | |
71 | bool 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 | |
104 | void 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 | |
139 | void 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 | |
168 | void 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 | |
182 | void RenderSVGImage::invalidateBufferedForeground() |
183 | { |
184 | m_bufferedForeground.reset(); |
185 | } |
186 | |
187 | bool 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 | |
213 | void 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 | |
233 | void 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 | |