| /* |
| * Copyright (C) 2021, 2022 Igalia S.L. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "SVGBoundingBoxComputation.h" |
| |
| #if ENABLE(LAYER_BASED_SVG_ENGINE) |
| #include "RenderChildIterator.h" |
| #include "RenderSVGContainer.h" |
| #include "RenderSVGForeignObject.h" |
| #include "RenderSVGImage.h" |
| #include "RenderSVGInline.h" |
| #include "RenderSVGResourceClipper.h" |
| #include "RenderSVGResourceFilter.h" |
| #include "RenderSVGResourceMarker.h" |
| #include "RenderSVGResourceMasker.h" |
| #include "RenderSVGRoot.h" |
| #include "RenderSVGShape.h" |
| #include "RenderSVGText.h" |
| #include "SVGResources.h" |
| #include "SVGResourcesCache.h" |
| |
| namespace WebCore { |
| |
| SVGBoundingBoxComputation::SVGBoundingBoxComputation(const RenderLayerModelObject& renderer) |
| : m_renderer(renderer) |
| { |
| } |
| |
| FloatRect SVGBoundingBoxComputation::computeDecoratedBoundingBox(const SVGBoundingBoxComputation::DecorationOptions& options, bool* boundingBoxValid) const |
| { |
| // SVG2: Bounding boxes algorithm (https://svgwg.org/svg2-draft/coords.html#BoundingBoxes) |
| |
| // The following algorithm defines how to compute a bounding box for a given element. The inputs to the algorithm are: |
| // - element, the element we are computing a bounding box for; |
| // - space, a coordinate space in which the bounding box will be computed; |
| // - fill, a boolean indicating whether the bounding box includes the geometry of the element and its descendants; |
| // - stroke, a boolean indicating whether the bounding box includes the stroke of the element and its descendants; |
| // - markers, a boolean indicating whether the bounding box includes the markers of the element and its descendants; and |
| // - clipped, a boolean indicating whether the bounding box is affected by any clipping paths applied to the element and its descendants. |
| |
| // The algorithm to compute the bounding box is as follows, depending on the type of element: |
| // - a shape (RenderSVGShape) |
| // - a text content element (RenderSVGText or RenderSVGInline) |
| // - an "a" element within a text content element (-> creates RenderSVGInline) |
| if (is<RenderSVGShape>(m_renderer) || is<RenderSVGText>(m_renderer) || is<RenderSVGInline>(m_renderer)) |
| return handleShapeOrTextOrInline(options, boundingBoxValid); |
| |
| // - a container element (RenderSVGRoot / RenderSVGContainer) |
| // - "use" (RenderSVGTransformableContainer) |
| if (is<RenderSVGRoot>(m_renderer) || is<RenderSVGContainer>(m_renderer)) |
| return handleRootOrContainer(options, boundingBoxValid); |
| |
| // - "foreignObject" |
| // - "image" |
| // FIXME: [LBSE] Upstream new RenderSVGImage implementation |
| // if (is<RenderSVGForeignObject>(m_renderer) || is<RenderSVGImage>(m_renderer)) |
| if (is<RenderSVGForeignObject>(m_renderer)) |
| return handleForeignObjectOrImage(options, boundingBoxValid); |
| |
| ASSERT_NOT_REACHED(); |
| return FloatRect(); |
| } |
| |
| FloatRect SVGBoundingBoxComputation::handleShapeOrTextOrInline(const SVGBoundingBoxComputation::DecorationOptions& options, bool* boundingBoxValid) const |
| { |
| // 1. Let box be a rectangle initialized to (0, 0, 0, 0). |
| FloatRect box; |
| |
| // 2. Let fill-shape be the equivalent path of element if it is a shape, or a shape that includes each of the |
| // glyph cells corresponding to the text within the elements otherwise. |
| // 3. If fill is true, then set box to the tightest rectangle in the coordinate system space that contains fill-shape. |
| // |
| // Note: The values of the fill, fill-opacity and fill-rule properties do not affect fill-shape. |
| if (options.contains(DecorationOption::IncludeFillShape)) |
| box = m_renderer.objectBoundingBox(); |
| |
| // 4. If stroke is true and the element's stroke is anything other than none, then set box to be the union of box |
| // and the tightest rectangle in coordinate system space that contains the stroke shape of the element, with the |
| // assumption that the element has no dash pattern. |
| // |
| // Note: The values of the stroke-opacity, stroke-dasharray and stroke-dashoffset do not affect the calculation of the stroke shape. |
| if (options.contains(DecorationOption::IncludeStrokeShape)) |
| box.unite(m_renderer.strokeBoundingBox()); |
| |
| // 5. If markers is true, then for each marker marker rendered on the element: |
| // - For each descendant graphics element child of the "marker" element that defines marker's content: |
| // - If child has an ancestor element within the "marker" that is 'display: none', has a failing conditional processing attribute, |
| // or is not an "a", "g", "svg" or "switch" element, then continue to the next descendant graphics element. |
| // - Otherwise, set box to be the union of box and the result of invoking the algorithm to compute a bounding box with child as |
| // the element, space as the target coordinate space, true for fill, stroke and markers, and clipped for clipped. |
| if (options.contains(DecorationOption::IncludeMarkers) && is<RenderSVGShape>(m_renderer)) { |
| DecorationOptions optionsForMarker = { DecorationOption::IncludeFillShape, DecorationOption::IncludeStrokeShape, DecorationOption::IncludeMarkers }; |
| if (options.contains(DecorationOption::IncludeClippers)) |
| optionsForMarker.add(DecorationOption::IncludeClippers); |
| box.unite(downcast<RenderSVGShape>(m_renderer).computeMarkerBoundingBox(options)); |
| } |
| |
| // 6. If clipped is true and the value of clip-path on element is not none, then set box to be the tightest rectangle |
| // in coordinate system space that contains the intersection of box and the clipping path. |
| adjustBoxForClippingAndEffects(options, box); |
| |
| // 7. Return box. |
| if (boundingBoxValid) |
| *boundingBoxValid = true; |
| return box; |
| } |
| |
| FloatRect SVGBoundingBoxComputation::handleRootOrContainer(const SVGBoundingBoxComputation::DecorationOptions& options, bool* boundingBoxValid) const |
| { |
| auto transformationMatrixFromChild = [] (const RenderLayerModelObject& child) -> std::optional<TransformationMatrix> { |
| if (!child.hasTransform()) |
| return std::nullopt; |
| |
| auto* container = child.parent(); |
| ASSERT(container); |
| |
| bool containerSkipped = false; |
| ASSERT(container == child.container(nullptr, containerSkipped)); |
| ASSERT_UNUSED(containerSkipped, !containerSkipped); |
| |
| TransformationMatrix layerTransform; |
| child.getTransformFromContainer(container, LayoutSize(), layerTransform); |
| return layerTransform.isIdentity() ? std::nullopt : std::make_optional(WTFMove(layerTransform)); |
| }; |
| |
| auto uniteBoundingBoxRespectingValidity = [] (bool& boxValid, FloatRect& box, const RenderLayerModelObject& child, const FloatRect& childBoundingBox) { |
| bool isBoundingBoxValid = is<RenderSVGContainer>(child) ? downcast<RenderSVGContainer>(child).isObjectBoundingBoxValid() : true; |
| if (!isBoundingBoxValid) |
| return; |
| |
| if (boxValid) { |
| box.uniteEvenIfEmpty(childBoundingBox); |
| return; |
| } |
| |
| box = childBoundingBox; |
| boxValid = true; |
| }; |
| |
| // 1. Let box be a rectangle initialized to (0, 0, 0, 0). |
| FloatRect box; |
| bool boxValid = false; |
| |
| // 2. Let parent be the container element if it is one, or the root of the "use" element's shadow tree otherwise. |
| |
| // 3. For each descendant graphics element child of parent: |
| // - If child is not rendered then continue to the next descendant graphics element. |
| // - Otherwise, set box to be the union of box and the result of invoking the algorithm to compute a bounding box with child |
| // as the element and the same values for space, fill, stroke, markers and clipped as the corresponding algorithm input values. |
| for (auto& child : childrenOfType<RenderLayerModelObject>(m_renderer)) { |
| // FIXME: [LBSE] Upstream new RenderSVGHiddenContainer implementation |
| // if (is<RenderSVGHiddenContainer>(child) || (is<RenderSVGShape>(child) && downcast<RenderSVGShape>(child).isRenderingDisabled())) |
| if (is<RenderSVGShape>(child) && downcast<RenderSVGShape>(child).isRenderingDisabled()) |
| continue; |
| |
| SVGBoundingBoxComputation childBoundingBoxComputation(child); |
| auto childBox = childBoundingBoxComputation.computeDecoratedBoundingBox(options); |
| if (options.contains(DecorationOption::OverrideBoxWithFilterBoxForChildren) && is<RenderSVGContainer>(child)) |
| childBoundingBoxComputation.adjustBoxForClippingAndEffects({ DecorationOption::OverrideBoxWithFilterBox }, childBox); |
| |
| if (!options.contains(DecorationOption::IgnoreTransformations)) { |
| if (auto layerTransform = transformationMatrixFromChild(child)) |
| childBox = layerTransform->mapRect(childBox); |
| } |
| |
| if (options == objectBoundingBoxDecoration) |
| uniteBoundingBoxRespectingValidity(boxValid, box, child, childBox); |
| else |
| box.unite(childBox); |
| } |
| |
| // 4. If clipped is true: |
| // - If the value of clip-path on element is not none, then set box to be the tightest rectangle in coordinate system space that |
| // contains the intersection of box and the clipping path. |
| // - If the overflow property applies to the element and does not have a value of visible, then set box to be the tightest rectangle |
| // in coordinate system space that contains the intersection of box and the element's overflow bounds. |
| // - If the clip property applies to the element and does not have a value of auto, then set box to be the tightest rectangle in coordinate |
| // system space that contains the intersection of box and the rectangle specified by clip. (TODO!) |
| adjustBoxForClippingAndEffects(options, box, { DecorationOption::OverrideBoxWithFilterBox }); |
| |
| if (options.contains(DecorationOption::IncludeClippers) && m_renderer.hasNonVisibleOverflow()) { |
| ASSERT(m_renderer.hasLayer()); |
| |
| // FIXME: [LBSE] Upstream new RenderSVGViewportContainer / RenderSVGResourceMarker implementation |
| // ASSERT(is<RenderSVGViewportContainer>(m_renderer) || is<RenderSVGResourceMarker>(m_renderer) || is<RenderSVGRoot>(m_renderer)); |
| ASSERT(is<RenderSVGRoot>(m_renderer)); |
| |
| LayoutRect overflowClipRect; |
| if (is<RenderSVGModelObject>(m_renderer)) |
| overflowClipRect = downcast<RenderSVGModelObject>(m_renderer).overflowClipRect(LayoutPoint()); |
| else if (is<RenderBox>(m_renderer)) |
| overflowClipRect = downcast<RenderBox>(m_renderer).overflowClipRect(LayoutPoint()); |
| else { |
| ASSERT_NOT_REACHED(); |
| return FloatRect(); |
| } |
| |
| box.intersect(overflowClipRect); |
| } |
| |
| // 5. Return box. |
| if (boundingBoxValid) |
| *boundingBoxValid = boxValid; |
| return box; |
| } |
| |
| FloatRect SVGBoundingBoxComputation::handleForeignObjectOrImage(const SVGBoundingBoxComputation::DecorationOptions& options, bool* boundingBoxValid) const |
| { |
| // 1. Let box be the tightest rectangle in coordinate space space that contains the positioning rectangle |
| // defined by the "x", "y", "width" and "height" geometric properties of the element. |
| // |
| // Note: The fill, stroke and markers input arguments to this algorithm do not affect the bounding box returned for these elements. |
| auto box = m_renderer.objectBoundingBox(); |
| |
| // 2. If clipped is true and the value of clip-path on element is not none, then set box to be the tightest rectangle |
| // in coordinate system space that contains the intersection of box and the clipping path. |
| adjustBoxForClippingAndEffects(options, box); |
| |
| // 3. Return box. |
| if (boundingBoxValid) |
| *boundingBoxValid = true; |
| return box; |
| } |
| |
| void SVGBoundingBoxComputation::adjustBoxForClippingAndEffects(const SVGBoundingBoxComputation::DecorationOptions& options, FloatRect& box, const SVGBoundingBoxComputation::DecorationOptions& optionsToCheckForFilters) const |
| { |
| bool includeFilter = false; |
| for (auto filterOption : optionsToCheckForFilters) { |
| if (options.contains(filterOption)) { |
| includeFilter = true; |
| break; |
| } |
| } |
| |
| bool includeClipper = options.contains(DecorationOption::IncludeClippers); |
| bool includeMasker = options.contains(DecorationOption::IncludeMaskers); |
| |
| if (includeFilter || includeClipper || includeMasker) { |
| if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(m_renderer)) { |
| if (includeFilter) { |
| if (auto* filter = resources->filter()) |
| box = filter->resourceBoundingBox(m_renderer); |
| } |
| |
| if (includeClipper) { |
| if (auto* clipper = resources->clipper()) |
| box.intersect(clipper->resourceBoundingBox(m_renderer)); |
| } |
| |
| if (includeMasker) { |
| if (auto* masker = resources->masker()) |
| box.intersect(masker->resourceBoundingBox(m_renderer)); |
| } |
| } |
| } |
| |
| if (options.contains(DecorationOption::IncludeOutline)) |
| box.inflate(m_renderer.outlineStyleForRepaint().outlineSize()); |
| } |
| |
| } |
| |
| #endif |