1 | /* |
2 | * Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> |
3 | * Copyright (C) 2004, 2005, 2008 Rob Buis <buis@kde.org> |
4 | * Copyright (C) 2005, 2007 Eric Seidel <eric@webkit.org> |
5 | * Copyright (C) 2009 Google, Inc. |
6 | * Copyright (C) 2009 Dirk Schulze <krit@webkit.org> |
7 | * Copyright (C) Research In Motion Limited 2010. All rights reserved. |
8 | * Copyright (C) 2009 Jeff Schiller <codedread@gmail.com> |
9 | * Copyright (C) 2011 Renata Hodovan <reni@webkit.org> |
10 | * Copyright (C) 2011 University of Szeged |
11 | * Copyright (C) 2018 Adobe Systems Incorporated. All rights reserved. |
12 | * |
13 | * This library is free software; you can redistribute it and/or |
14 | * modify it under the terms of the GNU Library General Public |
15 | * License as published by the Free Software Foundation; either |
16 | * version 2 of the License, or (at your option) any later version. |
17 | * |
18 | * This library is distributed in the hope that it will be useful, |
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
21 | * Library General Public License for more details. |
22 | * |
23 | * You should have received a copy of the GNU Library General Public License |
24 | * along with this library; see the file COPYING.LIB. If not, write to |
25 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
26 | * Boston, MA 02110-1301, USA. |
27 | */ |
28 | |
29 | #include "config.h" |
30 | #include "RenderSVGShape.h" |
31 | |
32 | #include "FloatPoint.h" |
33 | #include "FloatQuad.h" |
34 | #include "GraphicsContext.h" |
35 | #include "HitTestRequest.h" |
36 | #include "HitTestResult.h" |
37 | #include "LayoutRepainter.h" |
38 | #include "PointerEventsHitRules.h" |
39 | #include "RenderSVGResourceMarker.h" |
40 | #include "RenderSVGResourceSolidColor.h" |
41 | #include "SVGPathData.h" |
42 | #include "SVGRenderingContext.h" |
43 | #include "SVGResources.h" |
44 | #include "SVGResourcesCache.h" |
45 | #include "SVGURIReference.h" |
46 | #include "StrokeStyleApplier.h" |
47 | #include <wtf/IsoMallocInlines.h> |
48 | #include <wtf/StackStats.h> |
49 | |
50 | namespace WebCore { |
51 | |
52 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGShape); |
53 | |
54 | class BoundingRectStrokeStyleApplier final : public StrokeStyleApplier { |
55 | public: |
56 | BoundingRectStrokeStyleApplier(const RenderSVGShape& renderer) |
57 | : m_renderer(renderer) |
58 | { |
59 | } |
60 | |
61 | void strokeStyle(GraphicsContext* context) override |
62 | { |
63 | SVGRenderSupport::applyStrokeStyleToContext(context, m_renderer.style(), m_renderer); |
64 | } |
65 | |
66 | private: |
67 | const RenderSVGShape& m_renderer; |
68 | }; |
69 | |
70 | RenderSVGShape::RenderSVGShape(SVGGraphicsElement& element, RenderStyle&& style) |
71 | : RenderSVGModelObject(element, WTFMove(style)) |
72 | , m_needsBoundariesUpdate(false) // Default is false, the cached rects are empty from the beginning. |
73 | , m_needsShapeUpdate(true) // Default is true, so we grab a Path object once from SVGGraphicsElement. |
74 | , m_needsTransformUpdate(true) // Default is true, so we grab a AffineTransform object once from SVGGraphicsElement. |
75 | { |
76 | } |
77 | |
78 | RenderSVGShape::~RenderSVGShape() = default; |
79 | |
80 | void RenderSVGShape::updateShapeFromElement() |
81 | { |
82 | m_path = std::make_unique<Path>(pathFromGraphicsElement(&graphicsElement())); |
83 | processMarkerPositions(); |
84 | |
85 | m_fillBoundingBox = calculateObjectBoundingBox(); |
86 | m_strokeBoundingBox = calculateStrokeBoundingBox(); |
87 | } |
88 | |
89 | bool RenderSVGShape::isEmpty() const |
90 | { |
91 | // This function should never be called before assigning a new Path to m_path. |
92 | // But this bug can happen if this renderer was created and its layout was not |
93 | // done before painting. Assert this did not happen but do not crash. |
94 | ASSERT(hasPath()); |
95 | return !hasPath() || path().isEmpty(); |
96 | } |
97 | |
98 | void RenderSVGShape::fillShape(GraphicsContext& context) const |
99 | { |
100 | context.fillPath(path()); |
101 | } |
102 | |
103 | void RenderSVGShape::strokeShape(GraphicsContext& context) const |
104 | { |
105 | ASSERT(m_path); |
106 | Path* usePath = m_path.get(); |
107 | |
108 | if (hasNonScalingStroke()) |
109 | usePath = nonScalingStrokePath(usePath, nonScalingStrokeTransform()); |
110 | |
111 | context.strokePath(*usePath); |
112 | } |
113 | |
114 | bool RenderSVGShape::shapeDependentStrokeContains(const FloatPoint& point, PointCoordinateSpace pointCoordinateSpace) |
115 | { |
116 | ASSERT(m_path); |
117 | BoundingRectStrokeStyleApplier applier(*this); |
118 | |
119 | if (hasNonScalingStroke() && pointCoordinateSpace != LocalCoordinateSpace) { |
120 | AffineTransform nonScalingTransform = nonScalingStrokeTransform(); |
121 | Path* usePath = nonScalingStrokePath(m_path.get(), nonScalingTransform); |
122 | |
123 | return usePath->strokeContains(&applier, nonScalingTransform.mapPoint(point)); |
124 | } |
125 | |
126 | return m_path->strokeContains(&applier, point); |
127 | } |
128 | |
129 | bool RenderSVGShape::shapeDependentFillContains(const FloatPoint& point, const WindRule fillRule) const |
130 | { |
131 | return path().contains(point, fillRule); |
132 | } |
133 | |
134 | bool RenderSVGShape::fillContains(const FloatPoint& point, bool requiresFill, const WindRule fillRule) |
135 | { |
136 | if (!m_fillBoundingBox.contains(point)) |
137 | return false; |
138 | |
139 | Color fallbackColor; |
140 | if (requiresFill && !RenderSVGResource::fillPaintingResource(*this, style(), fallbackColor)) |
141 | return false; |
142 | |
143 | return shapeDependentFillContains(point, fillRule); |
144 | } |
145 | |
146 | bool RenderSVGShape::strokeContains(const FloatPoint& point, bool requiresStroke) |
147 | { |
148 | if (!strokeBoundingBox().contains(point)) |
149 | return false; |
150 | |
151 | Color fallbackColor; |
152 | if (requiresStroke && !RenderSVGResource::strokePaintingResource(*this, style(), fallbackColor)) |
153 | return false; |
154 | |
155 | return shapeDependentStrokeContains(point); |
156 | } |
157 | |
158 | void RenderSVGShape::layout() |
159 | { |
160 | StackStats::LayoutCheckPoint layoutCheckPoint; |
161 | LayoutRepainter repainter(*this, SVGRenderSupport::checkForSVGRepaintDuringLayout(*this) && selfNeedsLayout()); |
162 | |
163 | bool updateCachedBoundariesInParents = false; |
164 | |
165 | if (m_needsShapeUpdate || m_needsBoundariesUpdate) { |
166 | updateShapeFromElement(); |
167 | m_needsShapeUpdate = false; |
168 | updateRepaintBoundingBox(); |
169 | m_needsBoundariesUpdate = false; |
170 | updateCachedBoundariesInParents = true; |
171 | } |
172 | |
173 | if (m_needsTransformUpdate) { |
174 | m_localTransform = graphicsElement().animatedLocalTransform(); |
175 | m_needsTransformUpdate = false; |
176 | updateCachedBoundariesInParents = true; |
177 | } |
178 | |
179 | // Invalidate all resources of this client if our layout changed. |
180 | if (everHadLayout() && selfNeedsLayout()) |
181 | SVGResourcesCache::clientLayoutChanged(*this); |
182 | |
183 | // If our bounds changed, notify the parents. |
184 | if (updateCachedBoundariesInParents) |
185 | RenderSVGModelObject::setNeedsBoundariesUpdate(); |
186 | |
187 | repainter.repaintAfterLayout(); |
188 | clearNeedsLayout(); |
189 | } |
190 | |
191 | Path* RenderSVGShape::nonScalingStrokePath(const Path* path, const AffineTransform& strokeTransform) const |
192 | { |
193 | static NeverDestroyed<Path> tempPath; |
194 | |
195 | tempPath.get() = *path; |
196 | tempPath.get().transform(strokeTransform); |
197 | |
198 | return &tempPath.get(); |
199 | } |
200 | |
201 | bool RenderSVGShape::setupNonScalingStrokeContext(AffineTransform& strokeTransform, GraphicsContextStateSaver& stateSaver) |
202 | { |
203 | Optional<AffineTransform> inverse = strokeTransform.inverse(); |
204 | if (!inverse) |
205 | return false; |
206 | |
207 | stateSaver.save(); |
208 | stateSaver.context()->concatCTM(inverse.value()); |
209 | return true; |
210 | } |
211 | |
212 | AffineTransform RenderSVGShape::nonScalingStrokeTransform() const |
213 | { |
214 | return graphicsElement().getScreenCTM(SVGLocatable::DisallowStyleUpdate); |
215 | } |
216 | |
217 | bool RenderSVGShape::shouldGenerateMarkerPositions() const |
218 | { |
219 | if (!style().svgStyle().hasMarkers()) |
220 | return false; |
221 | |
222 | if (!graphicsElement().supportsMarkers()) |
223 | return false; |
224 | |
225 | auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*this); |
226 | if (!resources) |
227 | return false; |
228 | |
229 | return resources->markerStart() || resources->markerMid() || resources->markerEnd(); |
230 | } |
231 | |
232 | void RenderSVGShape::fillShape(const RenderStyle& style, GraphicsContext& originalContext) |
233 | { |
234 | GraphicsContext* context = &originalContext; |
235 | Color fallbackColor; |
236 | if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(*this, style, fallbackColor)) { |
237 | if (fillPaintingResource->applyResource(*this, style, context, RenderSVGResourceMode::ApplyToFill)) |
238 | fillPaintingResource->postApplyResource(*this, context, RenderSVGResourceMode::ApplyToFill, nullptr, this); |
239 | else if (fallbackColor.isValid()) { |
240 | RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); |
241 | fallbackResource->setColor(fallbackColor); |
242 | if (fallbackResource->applyResource(*this, style, context, RenderSVGResourceMode::ApplyToFill)) |
243 | fallbackResource->postApplyResource(*this, context, RenderSVGResourceMode::ApplyToFill, nullptr, this); |
244 | } |
245 | } |
246 | } |
247 | |
248 | void RenderSVGShape::strokeShape(const RenderStyle& style, GraphicsContext& originalContext) |
249 | { |
250 | GraphicsContext* context = &originalContext; |
251 | Color fallbackColor; |
252 | if (RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(*this, style, fallbackColor)) { |
253 | if (strokePaintingResource->applyResource(*this, style, context, RenderSVGResourceMode::ApplyToStroke)) |
254 | strokePaintingResource->postApplyResource(*this, context, RenderSVGResourceMode::ApplyToStroke, nullptr, this); |
255 | else if (fallbackColor.isValid()) { |
256 | RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); |
257 | fallbackResource->setColor(fallbackColor); |
258 | if (fallbackResource->applyResource(*this, style, context, RenderSVGResourceMode::ApplyToStroke)) |
259 | fallbackResource->postApplyResource(*this, context, RenderSVGResourceMode::ApplyToStroke, nullptr, this); |
260 | } |
261 | } |
262 | } |
263 | |
264 | void RenderSVGShape::strokeShape(GraphicsContext& context) |
265 | { |
266 | if (!style().hasVisibleStroke()) |
267 | return; |
268 | |
269 | GraphicsContextStateSaver stateSaver(context, false); |
270 | if (hasNonScalingStroke()) { |
271 | AffineTransform nonScalingTransform = nonScalingStrokeTransform(); |
272 | if (!setupNonScalingStrokeContext(nonScalingTransform, stateSaver)) |
273 | return; |
274 | } |
275 | strokeShape(style(), context); |
276 | } |
277 | |
278 | void RenderSVGShape::fillStrokeMarkers(PaintInfo& childPaintInfo) |
279 | { |
280 | auto paintOrder = RenderStyle::paintTypesForPaintOrder(style().paintOrder()); |
281 | for (unsigned i = 0; i < paintOrder.size(); ++i) { |
282 | switch (paintOrder.at(i)) { |
283 | case PaintType::Fill: |
284 | fillShape(style(), childPaintInfo.context()); |
285 | break; |
286 | case PaintType::Stroke: |
287 | strokeShape(childPaintInfo.context()); |
288 | break; |
289 | case PaintType::Markers: |
290 | if (!m_markerPositions.isEmpty()) |
291 | drawMarkers(childPaintInfo); |
292 | break; |
293 | } |
294 | } |
295 | } |
296 | |
297 | void RenderSVGShape::paint(PaintInfo& paintInfo, const LayoutPoint&) |
298 | { |
299 | if (paintInfo.context().paintingDisabled() || paintInfo.phase != PaintPhase::Foreground |
300 | || style().visibility() == Visibility::Hidden || isEmpty()) |
301 | return; |
302 | FloatRect boundingBox = repaintRectInLocalCoordinates(); |
303 | if (!SVGRenderSupport::paintInfoIntersectsRepaintRect(boundingBox, m_localTransform, paintInfo)) |
304 | return; |
305 | |
306 | PaintInfo childPaintInfo(paintInfo); |
307 | GraphicsContextStateSaver stateSaver(childPaintInfo.context()); |
308 | childPaintInfo.applyTransform(m_localTransform); |
309 | |
310 | if (childPaintInfo.phase == PaintPhase::Foreground) { |
311 | SVGRenderingContext renderingContext(*this, childPaintInfo); |
312 | |
313 | if (renderingContext.isRenderingPrepared()) { |
314 | const SVGRenderStyle& svgStyle = style().svgStyle(); |
315 | if (svgStyle.shapeRendering() == ShapeRendering::CrispEdges) |
316 | childPaintInfo.context().setShouldAntialias(false); |
317 | |
318 | fillStrokeMarkers(childPaintInfo); |
319 | } |
320 | } |
321 | |
322 | if (style().outlineWidth()) |
323 | paintOutline(childPaintInfo, IntRect(boundingBox)); |
324 | } |
325 | |
326 | // This method is called from inside paintOutline() since we call paintOutline() |
327 | // while transformed to our coord system, return local coords |
328 | void RenderSVGShape::addFocusRingRects(Vector<LayoutRect>& rects, const LayoutPoint&, const RenderLayerModelObject*) |
329 | { |
330 | LayoutRect rect = LayoutRect(repaintRectInLocalCoordinates()); |
331 | if (!rect.isEmpty()) |
332 | rects.append(rect); |
333 | } |
334 | |
335 | bool RenderSVGShape::isPointInFill(const FloatPoint& point) |
336 | { |
337 | return shapeDependentFillContains(point, style().svgStyle().fillRule()); |
338 | } |
339 | |
340 | bool RenderSVGShape::isPointInStroke(const FloatPoint& point) |
341 | { |
342 | if (!style().svgStyle().hasStroke()) |
343 | return false; |
344 | |
345 | return shapeDependentStrokeContains(point, LocalCoordinateSpace); |
346 | } |
347 | |
348 | float RenderSVGShape::getTotalLength() const |
349 | { |
350 | if (m_path) |
351 | return m_path->length(); |
352 | |
353 | return 0; |
354 | } |
355 | |
356 | void RenderSVGShape::getPointAtLength(FloatPoint& point, float distance) const |
357 | { |
358 | if (!m_path) |
359 | return; |
360 | |
361 | bool isValid; |
362 | point = m_path->pointAtLength(distance, isValid); |
363 | } |
364 | |
365 | bool RenderSVGShape::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction) |
366 | { |
367 | // We only draw in the forground phase, so we only hit-test then. |
368 | if (hitTestAction != HitTestForeground) |
369 | return false; |
370 | |
371 | FloatPoint localPoint = m_localTransform.inverse().valueOr(AffineTransform()).mapPoint(pointInParent); |
372 | |
373 | if (!SVGRenderSupport::pointInClippingArea(*this, localPoint)) |
374 | return false; |
375 | |
376 | PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_PATH_HITTESTING, request, style().pointerEvents()); |
377 | bool isVisible = (style().visibility() == Visibility::Visible); |
378 | if (isVisible || !hitRules.requireVisible) { |
379 | const SVGRenderStyle& svgStyle = style().svgStyle(); |
380 | WindRule fillRule = svgStyle.fillRule(); |
381 | if (request.svgClipContent()) |
382 | fillRule = svgStyle.clipRule(); |
383 | if ((hitRules.canHitStroke && (svgStyle.hasStroke() || !hitRules.requireStroke) && strokeContains(localPoint, hitRules.requireStroke)) |
384 | || (hitRules.canHitFill && (svgStyle.hasFill() || !hitRules.requireFill) && fillContains(localPoint, hitRules.requireFill, fillRule))) { |
385 | updateHitTestResult(result, LayoutPoint(localPoint)); |
386 | if (result.addNodeToListBasedTestResult(&graphicsElement(), request, localPoint) == HitTestProgress::Stop) |
387 | return true; |
388 | } |
389 | } |
390 | return false; |
391 | } |
392 | |
393 | static inline RenderSVGResourceMarker* markerForType(SVGMarkerType type, RenderSVGResourceMarker* markerStart, RenderSVGResourceMarker* markerMid, RenderSVGResourceMarker* markerEnd) |
394 | { |
395 | switch (type) { |
396 | case StartMarker: |
397 | return markerStart; |
398 | case MidMarker: |
399 | return markerMid; |
400 | case EndMarker: |
401 | return markerEnd; |
402 | } |
403 | |
404 | ASSERT_NOT_REACHED(); |
405 | return 0; |
406 | } |
407 | |
408 | FloatRect RenderSVGShape::markerRect(float strokeWidth) const |
409 | { |
410 | ASSERT(!m_markerPositions.isEmpty()); |
411 | |
412 | auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*this); |
413 | ASSERT(resources); |
414 | |
415 | RenderSVGResourceMarker* markerStart = resources->markerStart(); |
416 | RenderSVGResourceMarker* markerMid = resources->markerMid(); |
417 | RenderSVGResourceMarker* markerEnd = resources->markerEnd(); |
418 | ASSERT(markerStart || markerMid || markerEnd); |
419 | |
420 | FloatRect boundaries; |
421 | unsigned size = m_markerPositions.size(); |
422 | for (unsigned i = 0; i < size; ++i) { |
423 | if (RenderSVGResourceMarker* marker = markerForType(m_markerPositions[i].type, markerStart, markerMid, markerEnd)) |
424 | boundaries.unite(marker->markerBoundaries(marker->markerTransformation(m_markerPositions[i].origin, m_markerPositions[i].angle, strokeWidth))); |
425 | } |
426 | return boundaries; |
427 | } |
428 | |
429 | FloatRect RenderSVGShape::calculateObjectBoundingBox() const |
430 | { |
431 | return path().boundingRect(); |
432 | } |
433 | |
434 | FloatRect RenderSVGShape::calculateStrokeBoundingBox() const |
435 | { |
436 | ASSERT(m_path); |
437 | FloatRect strokeBoundingBox = m_fillBoundingBox; |
438 | |
439 | const SVGRenderStyle& svgStyle = style().svgStyle(); |
440 | if (svgStyle.hasStroke()) { |
441 | BoundingRectStrokeStyleApplier strokeStyle(*this); |
442 | if (hasNonScalingStroke()) { |
443 | AffineTransform nonScalingTransform = nonScalingStrokeTransform(); |
444 | if (Optional<AffineTransform> inverse = nonScalingTransform.inverse()) { |
445 | Path* usePath = nonScalingStrokePath(m_path.get(), nonScalingTransform); |
446 | FloatRect strokeBoundingRect = usePath->strokeBoundingRect(&strokeStyle); |
447 | strokeBoundingRect = inverse.value().mapRect(strokeBoundingRect); |
448 | strokeBoundingBox.unite(strokeBoundingRect); |
449 | } |
450 | } else |
451 | strokeBoundingBox.unite(path().strokeBoundingRect(&strokeStyle)); |
452 | } |
453 | |
454 | if (!m_markerPositions.isEmpty()) |
455 | strokeBoundingBox.unite(markerRect(strokeWidth())); |
456 | |
457 | return strokeBoundingBox; |
458 | } |
459 | |
460 | void RenderSVGShape::updateRepaintBoundingBox() |
461 | { |
462 | m_repaintBoundingBoxExcludingShadow = strokeBoundingBox(); |
463 | SVGRenderSupport::intersectRepaintRectWithResources(*this, m_repaintBoundingBoxExcludingShadow); |
464 | |
465 | m_repaintBoundingBox = m_repaintBoundingBoxExcludingShadow; |
466 | } |
467 | |
468 | float RenderSVGShape::strokeWidth() const |
469 | { |
470 | SVGLengthContext lengthContext(&graphicsElement()); |
471 | return lengthContext.valueForLength(style().strokeWidth()); |
472 | } |
473 | |
474 | bool RenderSVGShape::hasSmoothStroke() const |
475 | { |
476 | const SVGRenderStyle& svgStyle = style().svgStyle(); |
477 | return svgStyle.strokeDashArray().isEmpty() |
478 | && style().strokeMiterLimit() == style().initialStrokeMiterLimit() |
479 | && style().joinStyle() == style().initialJoinStyle() |
480 | && style().capStyle() == style().initialCapStyle(); |
481 | } |
482 | |
483 | void RenderSVGShape::drawMarkers(PaintInfo& paintInfo) |
484 | { |
485 | ASSERT(!m_markerPositions.isEmpty()); |
486 | |
487 | auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*this); |
488 | if (!resources) |
489 | return; |
490 | |
491 | RenderSVGResourceMarker* markerStart = resources->markerStart(); |
492 | RenderSVGResourceMarker* markerMid = resources->markerMid(); |
493 | RenderSVGResourceMarker* markerEnd = resources->markerEnd(); |
494 | if (!markerStart && !markerMid && !markerEnd) |
495 | return; |
496 | |
497 | float strokeWidth = this->strokeWidth(); |
498 | unsigned size = m_markerPositions.size(); |
499 | for (unsigned i = 0; i < size; ++i) { |
500 | if (RenderSVGResourceMarker* marker = markerForType(m_markerPositions[i].type, markerStart, markerMid, markerEnd)) |
501 | marker->draw(paintInfo, marker->markerTransformation(m_markerPositions[i].origin, m_markerPositions[i].angle, strokeWidth)); |
502 | } |
503 | } |
504 | |
505 | void RenderSVGShape::processMarkerPositions() |
506 | { |
507 | m_markerPositions.clear(); |
508 | |
509 | if (!shouldGenerateMarkerPositions()) |
510 | return; |
511 | |
512 | ASSERT(m_path); |
513 | |
514 | SVGMarkerData markerData(m_markerPositions, SVGResourcesCache::cachedResourcesForRenderer(*this)->markerReverseStart()); |
515 | m_path->apply([&markerData](const PathElement& pathElement) { |
516 | SVGMarkerData::updateFromPathElement(markerData, pathElement); |
517 | }); |
518 | markerData.pathIsDone(); |
519 | } |
520 | |
521 | } |
522 | |