| 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 "SVGRenderSupport.h" |
| 28 | |
| 29 | #include "NodeRenderStyle.h" |
| 30 | #include "RenderChildIterator.h" |
| 31 | #include "RenderElement.h" |
| 32 | #include "RenderGeometryMap.h" |
| 33 | #include "RenderIterator.h" |
| 34 | #include "RenderLayer.h" |
| 35 | #include "RenderSVGImage.h" |
| 36 | #include "RenderSVGResourceClipper.h" |
| 37 | #include "RenderSVGResourceFilter.h" |
| 38 | #include "RenderSVGResourceMarker.h" |
| 39 | #include "RenderSVGResourceMasker.h" |
| 40 | #include "RenderSVGRoot.h" |
| 41 | #include "RenderSVGText.h" |
| 42 | #include "RenderSVGTransformableContainer.h" |
| 43 | #include "RenderSVGViewportContainer.h" |
| 44 | #include "SVGResources.h" |
| 45 | #include "SVGResourcesCache.h" |
| 46 | #include "TransformState.h" |
| 47 | |
| 48 | namespace WebCore { |
| 49 | |
| 50 | LayoutRect SVGRenderSupport::clippedOverflowRectForRepaint(const RenderElement& renderer, const RenderLayerModelObject* repaintContainer) |
| 51 | { |
| 52 | // Return early for any cases where we don't actually paint |
| 53 | if (renderer.style().visibility() != Visibility::Visible && !renderer.enclosingLayer()->hasVisibleContent()) |
| 54 | return LayoutRect(); |
| 55 | |
| 56 | // Pass our local paint rect to computeFloatVisibleRectInContainer() which will |
| 57 | // map to parent coords and recurse up the parent chain. |
| 58 | FloatRect repaintRect = renderer.repaintRectInLocalCoordinates(); |
| 59 | const SVGRenderStyle& svgStyle = renderer.style().svgStyle(); |
| 60 | if (const ShadowData* shadow = svgStyle.shadow()) |
| 61 | shadow->adjustRectForShadow(repaintRect); |
| 62 | return enclosingLayoutRect(renderer.computeFloatRectForRepaint(repaintRect, repaintContainer)); |
| 63 | } |
| 64 | |
| 65 | Optional<FloatRect> SVGRenderSupport::computeFloatVisibleRectInContainer(const RenderElement& renderer, const FloatRect& rect, const RenderLayerModelObject* container, RenderObject::VisibleRectContext context) |
| 66 | { |
| 67 | FloatRect adjustedRect = rect; |
| 68 | const SVGRenderStyle& svgStyle = renderer.style().svgStyle(); |
| 69 | if (const ShadowData* shadow = svgStyle.shadow()) |
| 70 | shadow->adjustRectForShadow(adjustedRect); |
| 71 | adjustedRect.inflate(renderer.style().outlineWidth()); |
| 72 | |
| 73 | // Translate to coords in our parent renderer, and then call computeFloatVisibleRectInContainer() on our parent. |
| 74 | adjustedRect = renderer.localToParentTransform().mapRect(adjustedRect); |
| 75 | return renderer.parent()->computeFloatVisibleRectInContainer(adjustedRect, container, context); |
| 76 | } |
| 77 | |
| 78 | const RenderElement& SVGRenderSupport::localToParentTransform(const RenderElement& renderer, AffineTransform &transform) |
| 79 | { |
| 80 | ASSERT(renderer.parent()); |
| 81 | auto& parent = *renderer.parent(); |
| 82 | |
| 83 | // At the SVG/HTML boundary (aka RenderSVGRoot), we apply the localToBorderBoxTransform |
| 84 | // to map an element from SVG viewport coordinates to CSS box coordinates. |
| 85 | if (is<RenderSVGRoot>(parent)) |
| 86 | transform = downcast<RenderSVGRoot>(parent).localToBorderBoxTransform() * renderer.localToParentTransform(); |
| 87 | else |
| 88 | transform = renderer.localToParentTransform(); |
| 89 | |
| 90 | return parent; |
| 91 | } |
| 92 | |
| 93 | void SVGRenderSupport::mapLocalToContainer(const RenderElement& renderer, const RenderLayerModelObject* repaintContainer, TransformState& transformState, bool* wasFixed) |
| 94 | { |
| 95 | AffineTransform transform; |
| 96 | auto& parent = localToParentTransform(renderer, transform); |
| 97 | |
| 98 | transformState.applyTransform(transform); |
| 99 | |
| 100 | MapCoordinatesFlags mode = UseTransforms; |
| 101 | parent.mapLocalToContainer(repaintContainer, transformState, mode, wasFixed); |
| 102 | } |
| 103 | |
| 104 | const RenderElement* SVGRenderSupport::pushMappingToContainer(const RenderElement& renderer, const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap) |
| 105 | { |
| 106 | ASSERT_UNUSED(ancestorToStopAt, ancestorToStopAt != &renderer); |
| 107 | |
| 108 | AffineTransform transform; |
| 109 | auto& parent = localToParentTransform(renderer, transform); |
| 110 | |
| 111 | geometryMap.push(&renderer, transform); |
| 112 | return &parent; |
| 113 | } |
| 114 | |
| 115 | bool SVGRenderSupport::checkForSVGRepaintDuringLayout(const RenderElement& renderer) |
| 116 | { |
| 117 | if (!renderer.checkForRepaintDuringLayout()) |
| 118 | return false; |
| 119 | // When a parent container is transformed in SVG, all children will be painted automatically |
| 120 | // so we are able to skip redundant repaint checks. |
| 121 | auto parent = renderer.parent(); |
| 122 | return !(is<RenderSVGContainer>(parent) && downcast<RenderSVGContainer>(*parent).didTransformToRootUpdate()); |
| 123 | } |
| 124 | |
| 125 | // Update a bounding box taking into account the validity of the other bounding box. |
| 126 | static inline void updateObjectBoundingBox(FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, const RenderObject* other, FloatRect otherBoundingBox) |
| 127 | { |
| 128 | bool otherValid = is<RenderSVGContainer>(*other) ? downcast<RenderSVGContainer>(*other).isObjectBoundingBoxValid() : true; |
| 129 | if (!otherValid) |
| 130 | return; |
| 131 | |
| 132 | if (!objectBoundingBoxValid) { |
| 133 | objectBoundingBox = otherBoundingBox; |
| 134 | objectBoundingBoxValid = true; |
| 135 | return; |
| 136 | } |
| 137 | |
| 138 | objectBoundingBox.uniteEvenIfEmpty(otherBoundingBox); |
| 139 | } |
| 140 | |
| 141 | void SVGRenderSupport::computeContainerBoundingBoxes(const RenderElement& container, FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, FloatRect& strokeBoundingBox, FloatRect& repaintBoundingBox) |
| 142 | { |
| 143 | objectBoundingBox = FloatRect(); |
| 144 | objectBoundingBoxValid = false; |
| 145 | strokeBoundingBox = FloatRect(); |
| 146 | |
| 147 | // When computing the strokeBoundingBox, we use the repaintRects of the container's children so that the container's stroke includes |
| 148 | // the resources applied to the children (such as clips and filters). This allows filters applied to containers to correctly bound |
| 149 | // the children, and also improves inlining of SVG content, as the stroke bound is used in that situation also. |
| 150 | for (auto& current : childrenOfType<RenderObject>(container)) { |
| 151 | if (current.isSVGHiddenContainer()) |
| 152 | continue; |
| 153 | |
| 154 | // Don't include elements in the union that do not render. |
| 155 | if (is<RenderSVGShape>(current) && downcast<RenderSVGShape>(current).isRenderingDisabled()) |
| 156 | continue; |
| 157 | |
| 158 | const AffineTransform& transform = current.localToParentTransform(); |
| 159 | if (transform.isIdentity()) { |
| 160 | updateObjectBoundingBox(objectBoundingBox, objectBoundingBoxValid, ¤t, current.objectBoundingBox()); |
| 161 | strokeBoundingBox.unite(current.repaintRectInLocalCoordinates()); |
| 162 | } else { |
| 163 | updateObjectBoundingBox(objectBoundingBox, objectBoundingBoxValid, ¤t, transform.mapRect(current.objectBoundingBox())); |
| 164 | strokeBoundingBox.unite(transform.mapRect(current.repaintRectInLocalCoordinates())); |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | repaintBoundingBox = strokeBoundingBox; |
| 169 | } |
| 170 | |
| 171 | bool SVGRenderSupport::paintInfoIntersectsRepaintRect(const FloatRect& localRepaintRect, const AffineTransform& localTransform, const PaintInfo& paintInfo) |
| 172 | { |
| 173 | if (localTransform.isIdentity()) |
| 174 | return localRepaintRect.intersects(paintInfo.rect); |
| 175 | |
| 176 | return localTransform.mapRect(localRepaintRect).intersects(paintInfo.rect); |
| 177 | } |
| 178 | |
| 179 | RenderSVGRoot* SVGRenderSupport::findTreeRootObject(RenderElement& start) |
| 180 | { |
| 181 | return lineageOfType<RenderSVGRoot>(start).first(); |
| 182 | } |
| 183 | |
| 184 | const RenderSVGRoot* SVGRenderSupport::findTreeRootObject(const RenderElement& start) |
| 185 | { |
| 186 | return lineageOfType<RenderSVGRoot>(start).first(); |
| 187 | } |
| 188 | |
| 189 | static inline void invalidateResourcesOfChildren(RenderElement& renderer) |
| 190 | { |
| 191 | ASSERT(!renderer.needsLayout()); |
| 192 | if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer)) |
| 193 | resources->removeClientFromCache(renderer, false); |
| 194 | |
| 195 | for (auto& child : childrenOfType<RenderElement>(renderer)) |
| 196 | invalidateResourcesOfChildren(child); |
| 197 | } |
| 198 | |
| 199 | static inline bool layoutSizeOfNearestViewportChanged(const RenderElement& renderer) |
| 200 | { |
| 201 | const RenderElement* start = &renderer; |
| 202 | while (start && !is<RenderSVGRoot>(*start) && !is<RenderSVGViewportContainer>(*start)) |
| 203 | start = start->parent(); |
| 204 | |
| 205 | ASSERT(start); |
| 206 | if (is<RenderSVGViewportContainer>(*start)) |
| 207 | return downcast<RenderSVGViewportContainer>(*start).isLayoutSizeChanged(); |
| 208 | |
| 209 | return downcast<RenderSVGRoot>(*start).isLayoutSizeChanged(); |
| 210 | } |
| 211 | |
| 212 | bool SVGRenderSupport::transformToRootChanged(RenderElement* ancestor) |
| 213 | { |
| 214 | while (ancestor && !is<RenderSVGRoot>(*ancestor)) { |
| 215 | if (is<RenderSVGTransformableContainer>(*ancestor)) |
| 216 | return downcast<RenderSVGTransformableContainer>(*ancestor).didTransformToRootUpdate(); |
| 217 | if (is<RenderSVGViewportContainer>(*ancestor)) |
| 218 | return downcast<RenderSVGViewportContainer>(*ancestor).didTransformToRootUpdate(); |
| 219 | ancestor = ancestor->parent(); |
| 220 | } |
| 221 | |
| 222 | return false; |
| 223 | } |
| 224 | |
| 225 | void SVGRenderSupport::layoutDifferentRootIfNeeded(const RenderElement& renderer) |
| 226 | { |
| 227 | if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer)) { |
| 228 | auto* svgRoot = SVGRenderSupport::findTreeRootObject(renderer); |
| 229 | ASSERT(svgRoot); |
| 230 | resources->layoutDifferentRootIfNeeded(svgRoot); |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | void SVGRenderSupport::layoutChildren(RenderElement& start, bool selfNeedsLayout) |
| 235 | { |
| 236 | bool layoutSizeChanged = layoutSizeOfNearestViewportChanged(start); |
| 237 | bool transformChanged = transformToRootChanged(&start); |
| 238 | HashSet<RenderElement*> elementsThatDidNotReceiveLayout; |
| 239 | |
| 240 | for (auto& child : childrenOfType<RenderObject>(start)) { |
| 241 | bool needsLayout = selfNeedsLayout; |
| 242 | bool childEverHadLayout = child.everHadLayout(); |
| 243 | |
| 244 | if (transformChanged) { |
| 245 | // If the transform changed we need to update the text metrics (note: this also happens for layoutSizeChanged=true). |
| 246 | if (is<RenderSVGText>(child)) |
| 247 | downcast<RenderSVGText>(child).setNeedsTextMetricsUpdate(); |
| 248 | needsLayout = true; |
| 249 | } |
| 250 | |
| 251 | if (layoutSizeChanged && is<SVGElement>(*child.node())) { |
| 252 | // When selfNeedsLayout is false and the layout size changed, we have to check whether this child uses relative lengths |
| 253 | auto& element = downcast<SVGElement>(*child.node()); |
| 254 | if (element.hasRelativeLengths()) { |
| 255 | // When the layout size changed and when using relative values tell the RenderSVGShape to update its shape object |
| 256 | if (is<RenderSVGShape>(child)) |
| 257 | downcast<RenderSVGShape>(child).setNeedsShapeUpdate(); |
| 258 | else if (is<RenderSVGText>(child)) { |
| 259 | auto& svgText = downcast<RenderSVGText>(child); |
| 260 | svgText.setNeedsTextMetricsUpdate(); |
| 261 | svgText.setNeedsPositioningValuesUpdate(); |
| 262 | } |
| 263 | |
| 264 | needsLayout = true; |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | if (needsLayout) |
| 269 | child.setNeedsLayout(MarkOnlyThis); |
| 270 | |
| 271 | if (child.needsLayout()) { |
| 272 | layoutDifferentRootIfNeeded(downcast<RenderElement>(child)); |
| 273 | downcast<RenderElement>(child).layout(); |
| 274 | // Renderers are responsible for repainting themselves when changing, except |
| 275 | // for the initial paint to avoid potential double-painting caused by non-sensical "old" bounds. |
| 276 | // We could handle this in the individual objects, but for now it's easier to have |
| 277 | // parent containers call repaint(). (RenderBlock::layout* has similar logic.) |
| 278 | if (!childEverHadLayout) |
| 279 | child.repaint(); |
| 280 | } else if (layoutSizeChanged && is<RenderElement>(child)) |
| 281 | elementsThatDidNotReceiveLayout.add(&downcast<RenderElement>(child)); |
| 282 | |
| 283 | ASSERT(!child.needsLayout()); |
| 284 | } |
| 285 | |
| 286 | if (!layoutSizeChanged) { |
| 287 | ASSERT(elementsThatDidNotReceiveLayout.isEmpty()); |
| 288 | return; |
| 289 | } |
| 290 | |
| 291 | // If the layout size changed, invalidate all resources of all children that didn't go through the layout() code path. |
| 292 | for (auto* element : elementsThatDidNotReceiveLayout) |
| 293 | invalidateResourcesOfChildren(*element); |
| 294 | } |
| 295 | |
| 296 | bool SVGRenderSupport::isOverflowHidden(const RenderElement& renderer) |
| 297 | { |
| 298 | // RenderSVGRoot should never query for overflow state - it should always clip itself to the initial viewport size. |
| 299 | ASSERT(!renderer.isDocumentElementRenderer()); |
| 300 | |
| 301 | return renderer.style().overflowX() == Overflow::Hidden || renderer.style().overflowX() == Overflow::Scroll; |
| 302 | } |
| 303 | |
| 304 | void SVGRenderSupport::intersectRepaintRectWithResources(const RenderElement& renderer, FloatRect& repaintRect) |
| 305 | { |
| 306 | auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer); |
| 307 | if (!resources) |
| 308 | return; |
| 309 | |
| 310 | if (RenderSVGResourceFilter* filter = resources->filter()) |
| 311 | repaintRect = filter->resourceBoundingBox(renderer); |
| 312 | |
| 313 | if (RenderSVGResourceClipper* clipper = resources->clipper()) |
| 314 | repaintRect.intersect(clipper->resourceBoundingBox(renderer)); |
| 315 | |
| 316 | if (RenderSVGResourceMasker* masker = resources->masker()) |
| 317 | repaintRect.intersect(masker->resourceBoundingBox(renderer)); |
| 318 | } |
| 319 | |
| 320 | bool SVGRenderSupport::filtersForceContainerLayout(const RenderElement& renderer) |
| 321 | { |
| 322 | // If any of this container's children need to be laid out, and a filter is applied |
| 323 | // to the container, we need to repaint the entire container. |
| 324 | if (!renderer.normalChildNeedsLayout()) |
| 325 | return false; |
| 326 | |
| 327 | auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer); |
| 328 | if (!resources || !resources->filter()) |
| 329 | return false; |
| 330 | |
| 331 | return true; |
| 332 | } |
| 333 | |
| 334 | inline FloatRect clipPathReferenceBox(const RenderElement& renderer, CSSBoxType boxType) |
| 335 | { |
| 336 | FloatRect referenceBox; |
| 337 | switch (boxType) { |
| 338 | case CSSBoxType::BorderBox: |
| 339 | case CSSBoxType::MarginBox: |
| 340 | case CSSBoxType::StrokeBox: |
| 341 | // FIXME: strokeBoundingBox() takes dasharray into account but shouldn't. |
| 342 | referenceBox = renderer.strokeBoundingBox(); |
| 343 | break; |
| 344 | case CSSBoxType::ViewBox: |
| 345 | if (renderer.element()) { |
| 346 | FloatSize viewportSize; |
| 347 | SVGLengthContext(downcast<SVGElement>(renderer.element())).determineViewport(viewportSize); |
| 348 | referenceBox.setSize(viewportSize); |
| 349 | break; |
| 350 | } |
| 351 | FALLTHROUGH; |
| 352 | case CSSBoxType::ContentBox: |
| 353 | case CSSBoxType::FillBox: |
| 354 | case CSSBoxType::PaddingBox: |
| 355 | case CSSBoxType::BoxMissing: |
| 356 | referenceBox = renderer.objectBoundingBox(); |
| 357 | break; |
| 358 | } |
| 359 | return referenceBox; |
| 360 | } |
| 361 | |
| 362 | inline bool isPointInCSSClippingArea(const RenderElement& renderer, const FloatPoint& point) |
| 363 | { |
| 364 | ClipPathOperation* clipPathOperation = renderer.style().clipPath(); |
| 365 | if (is<ShapeClipPathOperation>(clipPathOperation)) { |
| 366 | auto& clipPath = downcast<ShapeClipPathOperation>(*clipPathOperation); |
| 367 | FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox()); |
| 368 | if (!referenceBox.contains(point)) |
| 369 | return false; |
| 370 | return clipPath.pathForReferenceRect(referenceBox).contains(point, clipPath.windRule()); |
| 371 | } |
| 372 | if (is<BoxClipPathOperation>(clipPathOperation)) { |
| 373 | auto& clipPath = downcast<BoxClipPathOperation>(*clipPathOperation); |
| 374 | FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox()); |
| 375 | if (!referenceBox.contains(point)) |
| 376 | return false; |
| 377 | return clipPath.pathForReferenceRect(FloatRoundedRect {referenceBox}).contains(point); |
| 378 | } |
| 379 | |
| 380 | return true; |
| 381 | } |
| 382 | |
| 383 | void SVGRenderSupport::clipContextToCSSClippingArea(GraphicsContext& context, const RenderElement& renderer) |
| 384 | { |
| 385 | ClipPathOperation* clipPathOperation = renderer.style().clipPath(); |
| 386 | if (is<ShapeClipPathOperation>(clipPathOperation)) { |
| 387 | auto& clipPath = downcast<ShapeClipPathOperation>(*clipPathOperation); |
| 388 | FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox()); |
| 389 | context.clipPath(clipPath.pathForReferenceRect(referenceBox), clipPath.windRule()); |
| 390 | } |
| 391 | if (is<BoxClipPathOperation>(clipPathOperation)) { |
| 392 | auto& clipPath = downcast<BoxClipPathOperation>(*clipPathOperation); |
| 393 | FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox()); |
| 394 | context.clipPath(clipPath.pathForReferenceRect(FloatRoundedRect {referenceBox})); |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | bool SVGRenderSupport::pointInClippingArea(const RenderElement& renderer, const FloatPoint& point) |
| 399 | { |
| 400 | ClipPathOperation* clipPathOperation = renderer.style().clipPath(); |
| 401 | if (is<ShapeClipPathOperation>(clipPathOperation) || is<BoxClipPathOperation>(clipPathOperation)) |
| 402 | return isPointInCSSClippingArea(renderer, point); |
| 403 | |
| 404 | // We just take clippers into account to determine if a point is on the node. The Specification may |
| 405 | // change later and we also need to check maskers. |
| 406 | auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer); |
| 407 | if (!resources) |
| 408 | return true; |
| 409 | |
| 410 | if (RenderSVGResourceClipper* clipper = resources->clipper()) |
| 411 | return clipper->hitTestClipContent(renderer.objectBoundingBox(), point); |
| 412 | |
| 413 | return true; |
| 414 | } |
| 415 | |
| 416 | void SVGRenderSupport::applyStrokeStyleToContext(GraphicsContext* context, const RenderStyle& style, const RenderElement& renderer) |
| 417 | { |
| 418 | ASSERT(context); |
| 419 | ASSERT(renderer.element()); |
| 420 | ASSERT(renderer.element()->isSVGElement()); |
| 421 | |
| 422 | const SVGRenderStyle& svgStyle = style.svgStyle(); |
| 423 | |
| 424 | SVGLengthContext lengthContext(downcast<SVGElement>(renderer.element())); |
| 425 | context->setStrokeThickness(lengthContext.valueForLength(style.strokeWidth())); |
| 426 | context->setLineCap(style.capStyle()); |
| 427 | context->setLineJoin(style.joinStyle()); |
| 428 | if (style.joinStyle() == MiterJoin) |
| 429 | context->setMiterLimit(style.strokeMiterLimit()); |
| 430 | |
| 431 | const Vector<SVGLengthValue>& dashes = svgStyle.strokeDashArray(); |
| 432 | if (dashes.isEmpty()) |
| 433 | context->setStrokeStyle(SolidStroke); |
| 434 | else { |
| 435 | DashArray dashArray; |
| 436 | dashArray.reserveInitialCapacity(dashes.size()); |
| 437 | bool canSetLineDash = false; |
| 438 | |
| 439 | for (auto& dash : dashes) { |
| 440 | dashArray.uncheckedAppend(dash.value(lengthContext)); |
| 441 | if (dashArray.last() > 0) |
| 442 | canSetLineDash = true; |
| 443 | } |
| 444 | |
| 445 | if (canSetLineDash) |
| 446 | context->setLineDash(dashArray, lengthContext.valueForLength(svgStyle.strokeDashOffset())); |
| 447 | else |
| 448 | context->setStrokeStyle(SolidStroke); |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | void SVGRenderSupport::styleChanged(RenderElement& renderer, const RenderStyle* oldStyle) |
| 453 | { |
| 454 | #if ENABLE(CSS_COMPOSITING) |
| 455 | if (renderer.element() && renderer.element()->isSVGElement() && (!oldStyle || renderer.style().hasBlendMode() != oldStyle->hasBlendMode())) |
| 456 | SVGRenderSupport::updateMaskedAncestorShouldIsolateBlending(renderer); |
| 457 | #else |
| 458 | UNUSED_PARAM(renderer); |
| 459 | UNUSED_PARAM(oldStyle); |
| 460 | #endif |
| 461 | } |
| 462 | |
| 463 | #if ENABLE(CSS_COMPOSITING) |
| 464 | bool SVGRenderSupport::isolatesBlending(const RenderStyle& style) |
| 465 | { |
| 466 | return style.svgStyle().isolatesBlending() || style.hasFilter() || style.hasBlendMode() || style.opacity() < 1.0f; |
| 467 | } |
| 468 | |
| 469 | void SVGRenderSupport::updateMaskedAncestorShouldIsolateBlending(const RenderElement& renderer) |
| 470 | { |
| 471 | ASSERT(renderer.element()); |
| 472 | ASSERT(renderer.element()->isSVGElement()); |
| 473 | |
| 474 | bool maskedAncestorShouldIsolateBlending = renderer.style().hasBlendMode(); |
| 475 | for (auto* ancestor = renderer.element()->parentElement(); ancestor && ancestor->isSVGElement(); ancestor = ancestor->parentElement()) { |
| 476 | if (!downcast<SVGElement>(*ancestor).isSVGGraphicsElement()) |
| 477 | continue; |
| 478 | |
| 479 | const auto* style = ancestor->computedStyle(); |
| 480 | if (!style || !isolatesBlending(*style)) |
| 481 | continue; |
| 482 | |
| 483 | if (ancestor->computedStyle()->svgStyle().hasMasker()) |
| 484 | downcast<SVGGraphicsElement>(*ancestor).setShouldIsolateBlending(maskedAncestorShouldIsolateBlending); |
| 485 | |
| 486 | return; |
| 487 | } |
| 488 | } |
| 489 | #endif |
| 490 | } |
| 491 | |