blob: c1e6a527ab93ae3e1fb34002481aa03c72fe5f7d [file] [log] [blame]
/*
* Copyright (C) 2007, 2008 Rob Buis <buis@kde.org>
* Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
* Copyright (C) 2007 Eric Seidel <eric@webkit.org>
* Copyright (C) 2009 Google, Inc. All rights reserved.
* Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
* Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
*
* 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 "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
#include "third_party/blink/renderer/core/layout/layout_geometry_map.h"
#include "third_party/blink/renderer/core/layout/subtree_layout_scope.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_shape.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_text.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_transformable_container.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/style/shape_clip_path_operation.h"
#include "third_party/blink/renderer/core/svg/svg_element.h"
#include "third_party/blink/renderer/core/svg/svg_length_context.h"
#include "third_party/blink/renderer/platform/graphics/stroke_data.h"
#include "third_party/blink/renderer/platform/transforms/transform_state.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
namespace blink {
struct SearchCandidate {
SearchCandidate()
: layout_object(nullptr), distance(std::numeric_limits<float>::max()) {}
SearchCandidate(LayoutObject* layout_object, float distance)
: layout_object(layout_object), distance(distance) {}
LayoutObject* layout_object;
float distance;
};
FloatRect SVGLayoutSupport::LocalVisualRect(const LayoutObject& object) {
// For LayoutSVGRoot, use LayoutSVGRoot::localVisualRect() instead.
DCHECK(!object.IsSVGRoot());
// Return early for any cases where we don't actually paint
if (object.StyleRef().Visibility() != EVisibility::kVisible &&
!object.EnclosingLayer()->HasVisibleContent())
return FloatRect();
FloatRect visual_rect = object.VisualRectInLocalSVGCoordinates();
if (int outline_outset = object.StyleRef().OutlineOutsetExtent())
visual_rect.Inflate(outline_outset);
return visual_rect;
}
LayoutRect SVGLayoutSupport::VisualRectInAncestorSpace(
const LayoutObject& object,
const LayoutBoxModelObject& ancestor) {
LayoutRect rect;
MapToVisualRectInAncestorSpace(object, &ancestor, LocalVisualRect(object),
rect);
return rect;
}
LayoutRect SVGLayoutSupport::TransformVisualRect(
const LayoutObject& object,
const AffineTransform& root_transform,
const FloatRect& local_rect) {
FloatRect adjusted_rect = root_transform.MapRect(local_rect);
if (adjusted_rect.IsEmpty())
return LayoutRect();
// Use enclosingIntRect because we cannot properly apply subpixel offset of
// the SVGRoot since we don't know the desired subpixel accumulation at this
// point.
return LayoutRect(EnclosingIntRect(adjusted_rect));
}
static const LayoutSVGRoot& ComputeTransformToSVGRoot(
const LayoutObject& object,
AffineTransform& root_border_box_transform) {
DCHECK(object.IsSVGChild());
const LayoutObject* parent;
for (parent = &object; !parent->IsSVGRoot(); parent = parent->Parent())
root_border_box_transform.PreMultiply(parent->LocalToSVGParentTransform());
const LayoutSVGRoot& svg_root = ToLayoutSVGRoot(*parent);
root_border_box_transform.PreMultiply(svg_root.LocalToBorderBoxTransform());
return svg_root;
}
bool SVGLayoutSupport::MapToVisualRectInAncestorSpace(
const LayoutObject& object,
const LayoutBoxModelObject* ancestor,
const FloatRect& local_visual_rect,
LayoutRect& result_rect,
VisualRectFlags visual_rect_flags) {
AffineTransform root_border_box_transform;
const LayoutSVGRoot& svg_root =
ComputeTransformToSVGRoot(object, root_border_box_transform);
result_rect =
TransformVisualRect(object, root_border_box_transform, local_visual_rect);
// Apply initial viewport clip.
if (svg_root.ShouldApplyViewportClip()) {
LayoutRect clip_rect(svg_root.OverflowClipRect(LayoutPoint()));
if (visual_rect_flags & kEdgeInclusive) {
if (!result_rect.InclusiveIntersect(clip_rect))
return false;
} else {
result_rect.Intersect(clip_rect);
}
}
return svg_root.MapToVisualRectInAncestorSpace(ancestor, result_rect,
visual_rect_flags);
}
void SVGLayoutSupport::MapLocalToAncestor(const LayoutObject* object,
const LayoutBoxModelObject* ancestor,
TransformState& transform_state,
MapCoordinatesFlags flags) {
transform_state.ApplyTransform(object->LocalToSVGParentTransform());
LayoutObject* parent = object->Parent();
// At the SVG/HTML boundary (aka LayoutSVGRoot), we apply the
// localToBorderBoxTransform to map an element from SVG viewport coordinates
// to CSS box coordinates.
// LayoutSVGRoot's mapLocalToAncestor method expects CSS box coordinates.
if (parent->IsSVGRoot())
transform_state.ApplyTransform(
ToLayoutSVGRoot(parent)->LocalToBorderBoxTransform());
parent->MapLocalToAncestor(ancestor, transform_state, flags);
}
void SVGLayoutSupport::MapAncestorToLocal(const LayoutObject& object,
const LayoutBoxModelObject* ancestor,
TransformState& transform_state,
MapCoordinatesFlags flags) {
// |object| is either a LayoutSVGModelObject or a LayoutSVGBlock here. In
// the former case, |object| can never be an ancestor while in the latter
// the caller is responsible for doing the ancestor check. Because of this,
// computing the transform to the SVG root is always what we want to do here.
DCHECK_NE(ancestor, &object);
DCHECK(object.IsSVGContainer() || object.IsSVGShape() ||
object.IsSVGImage() || object.IsSVGText() ||
object.IsSVGForeignObject());
AffineTransform local_to_svg_root;
const LayoutSVGRoot& svg_root =
ComputeTransformToSVGRoot(object, local_to_svg_root);
MapCoordinatesFlags mode = flags | kUseTransforms | kApplyContainerFlip;
svg_root.MapAncestorToLocal(ancestor, transform_state, mode);
transform_state.ApplyTransform(local_to_svg_root);
}
const LayoutObject* SVGLayoutSupport::PushMappingToContainer(
const LayoutObject* object,
const LayoutBoxModelObject* ancestor_to_stop_at,
LayoutGeometryMap& geometry_map) {
DCHECK_NE(ancestor_to_stop_at, object);
LayoutObject* parent = object->Parent();
// At the SVG/HTML boundary (aka LayoutSVGRoot), we apply the
// localToBorderBoxTransform to map an element from SVG viewport coordinates
// to CSS box coordinates.
// LayoutSVGRoot's mapLocalToAncestor method expects CSS box coordinates.
if (parent->IsSVGRoot()) {
TransformationMatrix matrix(
ToLayoutSVGRoot(parent)->LocalToBorderBoxTransform());
matrix.Multiply(object->LocalToSVGParentTransform());
geometry_map.Push(object, matrix);
} else {
geometry_map.Push(object, object->LocalToSVGParentTransform());
}
return parent;
}
// Update a bounding box taking into account the validity of the other bounding
// box.
inline void SVGLayoutSupport::UpdateObjectBoundingBox(
FloatRect& object_bounding_box,
bool& object_bounding_box_valid,
LayoutObject* other,
FloatRect other_bounding_box) {
bool other_valid =
other->IsSVGContainer()
? ToLayoutSVGContainer(other)->IsObjectBoundingBoxValid()
: true;
if (!other_valid)
return;
if (!object_bounding_box_valid) {
object_bounding_box = other_bounding_box;
object_bounding_box_valid = true;
return;
}
object_bounding_box.UniteEvenIfEmpty(other_bounding_box);
}
void SVGLayoutSupport::ComputeContainerBoundingBoxes(
const LayoutObject* container,
FloatRect& object_bounding_box,
bool& object_bounding_box_valid,
FloatRect& stroke_bounding_box,
FloatRect& local_visual_rect) {
object_bounding_box = FloatRect();
object_bounding_box_valid = false;
stroke_bounding_box = FloatRect();
// When computing the strokeBoundingBox, we use the visualRects of
// the container's children so that the container's stroke includes the
// resources applied to the children (such as clips and filters). This allows
// filters applied to containers to correctly bound the children, and also
// improves inlining of SVG content, as the stroke bound is used in that
// situation also.
for (LayoutObject* current = container->SlowFirstChild(); current;
current = current->NextSibling()) {
if (current->IsSVGHiddenContainer())
continue;
// Don't include elements in the union that do not layout.
if (current->IsSVGShape() && ToLayoutSVGShape(current)->IsShapeEmpty())
continue;
if (current->IsSVGText() &&
!ToLayoutSVGText(current)->IsObjectBoundingBoxValid())
continue;
const AffineTransform& transform = current->LocalToSVGParentTransform();
UpdateObjectBoundingBox(object_bounding_box, object_bounding_box_valid,
current,
transform.MapRect(current->ObjectBoundingBox()));
stroke_bounding_box.Unite(
transform.MapRect(current->VisualRectInLocalSVGCoordinates()));
}
local_visual_rect = stroke_bounding_box;
AdjustVisualRectWithResources(*container, object_bounding_box,
local_visual_rect);
}
const LayoutSVGRoot* SVGLayoutSupport::FindTreeRootObject(
const LayoutObject* start) {
while (start && !start->IsSVGRoot())
start = start->Parent();
DCHECK(start);
DCHECK(start->IsSVGRoot());
return ToLayoutSVGRoot(start);
}
bool SVGLayoutSupport::LayoutSizeOfNearestViewportChanged(
const LayoutObject* start) {
for (; start; start = start->Parent()) {
if (start->IsSVGRoot())
return ToLayoutSVGRoot(start)->IsLayoutSizeChanged();
if (start->IsSVGViewportContainer())
return ToLayoutSVGViewportContainer(start)->IsLayoutSizeChanged();
}
NOTREACHED();
return false;
}
bool SVGLayoutSupport::ScreenScaleFactorChanged(const LayoutObject* ancestor) {
for (; ancestor; ancestor = ancestor->Parent()) {
if (ancestor->IsSVGRoot())
return ToLayoutSVGRoot(ancestor)->DidScreenScaleFactorChange();
if (ancestor->IsSVGTransformableContainer())
return ToLayoutSVGTransformableContainer(ancestor)
->DidScreenScaleFactorChange();
if (ancestor->IsSVGViewportContainer())
return ToLayoutSVGViewportContainer(ancestor)
->DidScreenScaleFactorChange();
}
NOTREACHED();
return false;
}
void SVGLayoutSupport::LayoutChildren(LayoutObject* first_child,
bool force_layout,
bool screen_scaling_factor_changed,
bool layout_size_changed) {
for (LayoutObject* child = first_child; child; child = child->NextSibling()) {
bool force_child_layout = force_layout;
if (screen_scaling_factor_changed) {
// If the screen scaling factor changed we need to update the text
// metrics (note: this also happens for layoutSizeChanged=true).
if (child->IsSVGText())
ToLayoutSVGText(child)->SetNeedsTextMetricsUpdate();
force_child_layout = true;
}
if (layout_size_changed) {
// When selfNeedsLayout is false and the layout size changed, we have to
// check whether this child uses relative lengths
if (SVGElement* element = child->GetNode()->IsSVGElement()
? ToSVGElement(child->GetNode())
: nullptr) {
if (element->HasRelativeLengths()) {
// FIXME: this should be done on invalidation, not during layout.
// When the layout size changed and when using relative values tell
// the LayoutSVGShape to update its shape object
if (child->IsSVGShape()) {
ToLayoutSVGShape(child)->SetNeedsShapeUpdate();
} else if (child->IsSVGText()) {
ToLayoutSVGText(child)->SetNeedsTextMetricsUpdate();
ToLayoutSVGText(child)->SetNeedsPositioningValuesUpdate();
}
force_child_layout = true;
}
}
}
// Resource containers are nasty: they can invalidate clients outside the
// current SubtreeLayoutScope.
// Since they only care about viewport size changes (to resolve their
// relative lengths), we trigger their invalidation directly from
// SVGSVGElement::svgAttributeChange() or at a higher SubtreeLayoutScope (in
// LayoutView::layout()). We do not create a SubtreeLayoutScope for
// resources because their ability to reference each other leads to circular
// layout. We protect against that within the layout code for resources, but
// it causes assertions if we use a SubTreeLayoutScope for them.
if (child->IsSVGResourceContainer()) {
// Lay out any referenced resources before the child.
LayoutResourcesIfNeeded(*child);
child->LayoutIfNeeded();
} else {
SubtreeLayoutScope layout_scope(*child);
if (force_child_layout)
layout_scope.SetNeedsLayout(child,
LayoutInvalidationReason::kSvgChanged);
// Lay out any referenced resources before the child.
LayoutResourcesIfNeeded(*child);
child->LayoutIfNeeded();
}
}
}
void SVGLayoutSupport::LayoutResourcesIfNeeded(const LayoutObject& object) {
SVGResources* resources =
SVGResourcesCache::CachedResourcesForLayoutObject(object);
if (resources)
resources->LayoutIfNeeded();
}
bool SVGLayoutSupport::IsOverflowHidden(const LayoutObject& object) {
// LayoutSVGRoot should never query for overflow state - it should always clip
// itself to the initial viewport size.
DCHECK(!object.IsDocumentElement());
return IsOverflowHidden(object.StyleRef());
}
bool SVGLayoutSupport::IsOverflowHidden(const ComputedStyle& style) {
return style.OverflowX() == EOverflow::kHidden ||
style.OverflowX() == EOverflow::kScroll;
}
void SVGLayoutSupport::AdjustVisualRectWithResources(
const LayoutObject& layout_object,
const FloatRect& object_bounding_box,
FloatRect& visual_rect) {
SVGResources* resources =
SVGResourcesCache::CachedResourcesForLayoutObject(layout_object);
if (!resources)
return;
if (LayoutSVGResourceFilter* filter = resources->Filter())
visual_rect = filter->ResourceBoundingBox(object_bounding_box);
if (LayoutSVGResourceClipper* clipper = resources->Clipper())
visual_rect.Intersect(clipper->ResourceBoundingBox(object_bounding_box));
if (LayoutSVGResourceMasker* masker = resources->Masker())
visual_rect.Intersect(masker->ResourceBoundingBox(object_bounding_box));
}
bool SVGLayoutSupport::HasFilterResource(const LayoutObject& object) {
SVGResources* resources =
SVGResourcesCache::CachedResourcesForLayoutObject(object);
return resources && resources->Filter();
}
bool SVGLayoutSupport::IntersectsClipPath(const LayoutObject& object,
const FloatPoint& point) {
ClipPathOperation* clip_path_operation = object.StyleRef().ClipPath();
if (!clip_path_operation)
return true;
if (clip_path_operation->GetType() == ClipPathOperation::SHAPE) {
ShapeClipPathOperation& clip_path =
ToShapeClipPathOperation(*clip_path_operation);
return clip_path.GetPath(object.ObjectBoundingBox()).Contains(point);
}
DCHECK_EQ(clip_path_operation->GetType(), ClipPathOperation::REFERENCE);
SVGResources* resources =
SVGResourcesCache::CachedResourcesForLayoutObject(object);
if (!resources || !resources->Clipper())
return true;
return resources->Clipper()->HitTestClipContent(object.ObjectBoundingBox(),
point);
}
const HitTestLocation* SVGLayoutSupport::TransformToUserSpaceAndCheckClipping(
const LayoutObject& object,
const AffineTransform& local_transform,
const HitTestLocation& location_in_parent,
base::Optional<HitTestLocation>& local_storage) {
// Use a fast path for an identity transform which creates no new
// HitTestLocation objects or inverse AffineTransforms, and performs no
// matrix multiplies.
if (local_transform.IsIdentity()) {
if (IntersectsClipPath(object, location_in_parent.TransformedPoint()))
return &location_in_parent;
return nullptr;
}
if (!local_transform.IsInvertible())
return nullptr;
const AffineTransform inverse = local_transform.Inverse();
if (location_in_parent.IsRectBasedTest()) {
local_storage.emplace(
HitTestLocation(inverse.MapPoint(location_in_parent.TransformedPoint()),
inverse.MapQuad(location_in_parent.TransformedRect())));
} else {
local_storage.emplace(HitTestLocation(
inverse.MapPoint(location_in_parent.TransformedPoint())));
}
if (IntersectsClipPath(object, local_storage->TransformedPoint()))
return &*local_storage;
return nullptr;
}
bool SVGLayoutSupport::HitTestChildren(LayoutObject* last_child,
HitTestResult& result,
const HitTestLocation& location,
const LayoutPoint& accumulated_offset,
HitTestAction hit_test_action) {
for (LayoutObject* child = last_child; child;
child = child->PreviousSibling()) {
if (child->IsSVGForeignObject()) {
if (ToLayoutSVGForeignObject(child)->NodeAtPointFromSVG(
result, location, accumulated_offset, hit_test_action))
return true;
} else {
if (child->NodeAtPoint(result, location, accumulated_offset,
hit_test_action))
return true;
}
}
return false;
}
DashArray SVGLayoutSupport::ResolveSVGDashArray(
const SVGDashArray& svg_dash_array,
const ComputedStyle& style,
const SVGLengthContext& length_context) {
DashArray dash_array;
for (const Length& dash_length : svg_dash_array.GetVector())
dash_array.push_back(length_context.ValueForLength(dash_length, style));
return dash_array;
}
void SVGLayoutSupport::ApplyStrokeStyleToStrokeData(StrokeData& stroke_data,
const ComputedStyle& style,
const LayoutObject& object,
float dash_scale_factor) {
DCHECK(object.GetNode());
DCHECK(object.GetNode()->IsSVGElement());
const SVGComputedStyle& svg_style = style.SvgStyle();
SVGLengthContext length_context(ToSVGElement(object.GetNode()));
stroke_data.SetThickness(
length_context.ValueForLength(svg_style.StrokeWidth()));
stroke_data.SetLineCap(svg_style.CapStyle());
stroke_data.SetLineJoin(svg_style.JoinStyle());
stroke_data.SetMiterLimit(svg_style.StrokeMiterLimit());
DashArray dash_array =
ResolveSVGDashArray(*svg_style.StrokeDashArray(), style, length_context);
float dash_offset =
length_context.ValueForLength(svg_style.StrokeDashOffset(), style);
// Apply scaling from 'pathLength'.
if (dash_scale_factor != 1) {
DCHECK_GE(dash_scale_factor, 0);
dash_offset *= dash_scale_factor;
for (auto& dash_item : dash_array)
dash_item *= dash_scale_factor;
}
stroke_data.SetLineDash(dash_array, dash_offset);
}
bool SVGLayoutSupport::IsLayoutableTextNode(const LayoutObject* object) {
DCHECK(object->IsText());
// <br> is marked as text, but is not handled by the SVG layout code-path.
return object->IsSVGInlineText() &&
!ToLayoutSVGInlineText(object)->HasEmptyText();
}
bool SVGLayoutSupport::WillIsolateBlendingDescendantsForStyle(
const ComputedStyle& style) {
const SVGComputedStyle& svg_style = style.SvgStyle();
return style.HasIsolation() || style.HasOpacity() || style.HasBlendMode() ||
style.HasFilter() || svg_style.HasMasker() || style.ClipPath();
}
bool SVGLayoutSupport::WillIsolateBlendingDescendantsForObject(
const LayoutObject* object) {
if (object->IsSVGHiddenContainer())
return false;
if (!object->IsSVGRoot() && !object->IsSVGContainer())
return false;
return WillIsolateBlendingDescendantsForStyle(object->StyleRef());
}
bool SVGLayoutSupport::IsIsolationRequired(const LayoutObject* object) {
return WillIsolateBlendingDescendantsForObject(object) &&
object->HasNonIsolatedBlendingDescendants();
}
AffineTransform::Transform
SubtreeContentTransformScope::current_content_transformation_ =
IDENTITY_TRANSFORM;
SubtreeContentTransformScope::SubtreeContentTransformScope(
const AffineTransform& subtree_content_transformation)
: saved_content_transformation_(current_content_transformation_) {
AffineTransform content_transformation =
subtree_content_transformation *
AffineTransform(current_content_transformation_);
content_transformation.CopyTransformTo(current_content_transformation_);
}
SubtreeContentTransformScope::~SubtreeContentTransformScope() {
saved_content_transformation_.CopyTransformTo(
current_content_transformation_);
}
AffineTransform SVGLayoutSupport::DeprecatedCalculateTransformToLayer(
const LayoutObject* layout_object) {
AffineTransform transform;
while (layout_object) {
transform = layout_object->LocalToSVGParentTransform() * transform;
if (layout_object->IsSVGRoot())
break;
layout_object = layout_object->Parent();
}
// Continue walking up the layer tree, accumulating CSS transforms.
// FIXME: this queries layer compositing state - which is not
// supported during layout. Hence, the result may not include all CSS
// transforms.
PaintLayer* layer = layout_object ? layout_object->EnclosingLayer() : nullptr;
while (layer && layer->IsAllowedToQueryCompositingState()) {
// We can stop at compositing layers, to match the backing resolution.
// FIXME: should we be computing the transform to the nearest composited
// layer, or the nearest composited layer that does not paint into its
// ancestor? I think this is the nearest composited ancestor since we will
// inherit its transforms in the composited layer tree.
if (layer->GetCompositingState() != kNotComposited)
break;
if (TransformationMatrix* layer_transform = layer->Transform())
transform = layer_transform->ToAffineTransform() * transform;
layer = layer->Parent();
}
return transform;
}
float SVGLayoutSupport::CalculateScreenFontSizeScalingFactor(
const LayoutObject* layout_object) {
DCHECK(layout_object);
// FIXME: trying to compute a device space transform at record time is wrong.
// All clients should be updated to avoid relying on this information, and the
// method should be removed.
AffineTransform ctm =
DeprecatedCalculateTransformToLayer(layout_object) *
SubtreeContentTransformScope::CurrentContentTransformation();
ctm.Scale(
layout_object->GetDocument().GetPage()->DeviceScaleFactorDeprecated());
return clampTo<float>(sqrt((ctm.XScaleSquared() + ctm.YScaleSquared()) / 2));
}
static inline bool CompareCandidateDistance(const SearchCandidate& r1,
const SearchCandidate& r2) {
return r1.distance < r2.distance;
}
static inline float DistanceToChildLayoutObject(LayoutObject* child,
const FloatPoint& point) {
const AffineTransform& local_to_parent_transform =
child->LocalToSVGParentTransform();
if (!local_to_parent_transform.IsInvertible())
return std::numeric_limits<float>::max();
FloatPoint child_local_point =
local_to_parent_transform.Inverse().MapPoint(point);
return child->ObjectBoundingBox().SquaredDistanceTo(child_local_point);
}
static SearchCandidate SearchTreeForFindClosestLayoutSVGText(
const LayoutObject* layout_object,
const FloatPoint& point) {
// Try to find the closest LayoutSVGText.
SearchCandidate closest_text;
Vector<SearchCandidate> candidates;
// Find the closest LayoutSVGText on this tree level, and also collect any
// containers that could contain LayoutSVGTexts that are closer.
for (LayoutObject* child = layout_object->SlowLastChild(); child;
child = child->PreviousSibling()) {
if (child->IsSVGText()) {
float distance = DistanceToChildLayoutObject(child, point);
if (distance >= closest_text.distance)
continue;
candidates.clear();
closest_text.layout_object = child;
closest_text.distance = distance;
continue;
}
if (child->IsSVGContainer() && !layout_object->IsSVGHiddenContainer()) {
float distance = DistanceToChildLayoutObject(child, point);
if (distance > closest_text.distance)
continue;
candidates.push_back(SearchCandidate(child, distance));
}
}
// If a LayoutSVGText was found and there are no potentially closer sub-trees,
// just return |closestText|.
if (closest_text.layout_object && candidates.IsEmpty())
return closest_text;
std::stable_sort(candidates.begin(), candidates.end(),
CompareCandidateDistance);
// Find the closest LayoutSVGText in the sub-trees in |candidates|.
// If a LayoutSVGText is found that is strictly closer than any previous
// candidate, then end the search.
for (const SearchCandidate& search_candidate : candidates) {
if (closest_text.distance < search_candidate.distance)
break;
LayoutObject* candidate_layout_object = search_candidate.layout_object;
FloatPoint candidate_local_point =
candidate_layout_object->LocalToSVGParentTransform().Inverse().MapPoint(
point);
SearchCandidate candidate_text = SearchTreeForFindClosestLayoutSVGText(
candidate_layout_object, candidate_local_point);
if (candidate_text.distance < closest_text.distance)
closest_text = candidate_text;
}
return closest_text;
}
LayoutObject* SVGLayoutSupport::FindClosestLayoutSVGText(
const LayoutObject* layout_object,
const FloatPoint& point) {
return SearchTreeForFindClosestLayoutSVGText(layout_object, point)
.layout_object;
}
} // namespace blink