blob: 31f880fb4ad871d0cc03f00cf36a706a396d2e37 [file] [log] [blame]
/*
* Copyright (C) Research In Motion Limited 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_resources.h"
#include "base/ranges/algorithm.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.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_paint_server.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_text.h"
#include "third_party/blink/renderer/core/paint/filter_effect_builder.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style/reference_clip_path_operation.h"
#include "third_party/blink/renderer/core/style/style_svg_resource.h"
#include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
#include "third_party/blink/renderer/core/svg/svg_filter_primitive_standard_attributes.h"
#include "third_party/blink/renderer/core/svg/svg_resource.h"
#include "third_party/blink/renderer/platform/graphics/filters/filter.h"
#include "third_party/blink/renderer/platform/graphics/filters/filter_effect.h"
#include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
#include "third_party/blink/renderer/platform/graphics/filters/source_graphic.h"
namespace blink {
SVGElementResourceClient* SVGResources::GetClient(const LayoutObject& object) {
return To<SVGElement>(object.GetNode())->GetSVGResourceClient();
}
FloatRect SVGResources::ReferenceBoxForEffects(
const LayoutObject& layout_object) {
// For SVG foreign objects, remove the position part of the bounding box. The
// position is already baked into the transform, and we don't want to re-apply
// the offset when, e.g., using "objectBoundingBox" for clipPathUnits.
// Use the frame size since it should have the proper zoom applied.
if (auto* foreign = DynamicTo<LayoutSVGForeignObject>(layout_object))
return FloatRect(FloatPoint::Zero(), FloatSize(foreign->Size()));
// Text "sub-elements" (<tspan>, <textpath>, <a>) should use the entire
// <text>s object bounding box rather then their own.
// https://svgwg.org/svg2-draft/text.html#ObjectBoundingBoxUnitsTextObjects
const LayoutObject* obb_layout_object = &layout_object;
if (layout_object.IsSVGInline()) {
obb_layout_object =
LayoutSVGText::LocateLayoutSVGTextAncestor(&layout_object);
}
DCHECK(obb_layout_object);
return obb_layout_object->ObjectBoundingBox();
}
void SVGResources::UpdateClipPathFilterMask(SVGElement& element,
const ComputedStyle* old_style,
const ComputedStyle& style) {
const bool had_client = element.GetSVGResourceClient();
if (auto* reference_clip =
DynamicTo<ReferenceClipPathOperation>(style.ClipPath()))
reference_clip->AddClient(element.EnsureSVGResourceClient());
if (style.HasFilter()) {
SVGElementResourceClient& client = element.EnsureSVGResourceClient();
style.Filter().AddClient(client);
LayoutObject* layout_object = element.GetLayoutObject();
// This is called from StyleDidChange so we should have a LayoutObject.
DCHECK(layout_object);
// TODO(fs): Reorganise the code so that we don't need to invalidate this
// again in SVGResourcesCache::ClientStyleChanged (and potentially avoid
// redundant invalidations).
layout_object->SetNeedsPaintPropertyUpdate();
client.MarkFilterDataDirty();
}
if (StyleSVGResource* masker_resource = style.MaskerResource())
masker_resource->AddClient(element.EnsureSVGResourceClient());
if (had_client)
ClearClipPathFilterMask(element, old_style);
}
void SVGResources::ClearClipPathFilterMask(SVGElement& element,
const ComputedStyle* style) {
if (!style)
return;
SVGElementResourceClient* client = element.GetSVGResourceClient();
if (!client)
return;
if (auto* old_reference_clip =
DynamicTo<ReferenceClipPathOperation>(style->ClipPath()))
old_reference_clip->RemoveClient(*client);
if (style->HasFilter()) {
style->Filter().RemoveClient(*client);
client->InvalidateFilterData();
}
if (StyleSVGResource* masker_resource = style->MaskerResource())
masker_resource->RemoveClient(*client);
}
void SVGResources::UpdatePaints(SVGElement& element,
const ComputedStyle* old_style,
const ComputedStyle& style) {
const bool had_client = element.GetSVGResourceClient();
if (StyleSVGResource* paint_resource = style.FillPaint().Resource())
paint_resource->AddClient(element.EnsureSVGResourceClient());
if (StyleSVGResource* paint_resource = style.StrokePaint().Resource())
paint_resource->AddClient(element.EnsureSVGResourceClient());
if (had_client)
ClearPaints(element, old_style);
}
void SVGResources::ClearPaints(SVGElement& element,
const ComputedStyle* style) {
if (!style)
return;
SVGResourceClient* client = element.GetSVGResourceClient();
if (!client)
return;
if (StyleSVGResource* paint_resource = style->FillPaint().Resource())
paint_resource->RemoveClient(*client);
if (StyleSVGResource* paint_resource = style->StrokePaint().Resource())
paint_resource->RemoveClient(*client);
}
void SVGResources::UpdateMarkers(SVGElement& element,
const ComputedStyle* old_style,
const ComputedStyle& style) {
const bool had_client = element.GetSVGResourceClient();
if (StyleSVGResource* marker_resource = style.MarkerStartResource())
marker_resource->AddClient(element.EnsureSVGResourceClient());
if (StyleSVGResource* marker_resource = style.MarkerMidResource())
marker_resource->AddClient(element.EnsureSVGResourceClient());
if (StyleSVGResource* marker_resource = style.MarkerEndResource())
marker_resource->AddClient(element.EnsureSVGResourceClient());
if (had_client)
ClearMarkers(element, old_style);
}
void SVGResources::ClearMarkers(SVGElement& element,
const ComputedStyle* style) {
if (!style)
return;
SVGResourceClient* client = element.GetSVGResourceClient();
if (!client)
return;
if (StyleSVGResource* marker_resource = style->MarkerStartResource())
marker_resource->RemoveClient(*client);
if (StyleSVGResource* marker_resource = style->MarkerMidResource())
marker_resource->RemoveClient(*client);
if (StyleSVGResource* marker_resource = style->MarkerEndResource())
marker_resource->RemoveClient(*client);
}
class SVGElementResourceClient::FilterData final
: public GarbageCollected<SVGElementResourceClient::FilterData> {
public:
FilterData(FilterEffect* last_effect, SVGFilterGraphNodeMap* node_map)
: last_effect_(last_effect), node_map_(node_map) {}
sk_sp<PaintFilter> BuildPaintFilter() {
return paint_filter_builder::Build(last_effect_, kInterpolationSpaceSRGB);
}
// Perform a finegrained invalidation of the filter chain for the
// specified filter primitive and attribute. Returns false if no
// further invalidation is required, otherwise true.
bool Invalidate(SVGFilterPrimitiveStandardAttributes& primitive,
const QualifiedName& attribute) {
if (FilterEffect* effect = node_map_->EffectForElement(primitive)) {
if (!primitive.SetFilterEffectAttribute(effect, attribute))
return false; // No change
node_map_->InvalidateDependentEffects(effect);
}
return true;
}
void Dispose() {
node_map_ = nullptr;
if (last_effect_)
last_effect_->DisposeImageFiltersRecursive();
last_effect_ = nullptr;
}
void Trace(Visitor* visitor) const {
visitor->Trace(last_effect_);
visitor->Trace(node_map_);
}
private:
Member<FilterEffect> last_effect_;
Member<SVGFilterGraphNodeMap> node_map_;
};
SVGElementResourceClient::SVGElementResourceClient(SVGElement* element)
: element_(element), filter_data_dirty_(false) {}
namespace {
template <typename ContainerType>
bool ContainsResource(const ContainerType* container, SVGResource* resource) {
return container && container->Resource() == resource;
}
bool ContainsResource(const FilterOperations& operations,
SVGResource* resource) {
return base::ranges::any_of(
operations.Operations(), [resource](const FilterOperation* operation) {
return ContainsResource(DynamicTo<ReferenceFilterOperation>(operation),
resource);
});
}
} // namespace
void SVGElementResourceClient::ResourceContentChanged(SVGResource* resource) {
LayoutObject* layout_object = element_->GetLayoutObject();
if (!layout_object)
return;
const ComputedStyle& style = layout_object->StyleRef();
if (style.HasFilter() && ContainsResource(style.Filter(), resource)) {
InvalidateFilterData();
layout_object->SetShouldDoFullPaintInvalidation();
}
if (auto* container = DynamicTo<LayoutSVGResourceContainer>(layout_object)) {
container->RemoveAllClientsFromCache();
return;
}
if (ContainsResource(style.FillPaint().Resource(), resource) ||
ContainsResource(style.StrokePaint().Resource(), resource)) {
// Since LayoutSVGInlineTexts don't have SVGResources (they use their
// parent's), they will not be notified of changes to paint servers. So
// if the client is one that could have a LayoutSVGInlineText use a
// paint invalidation reason that will force paint invalidation of the
// entire <text>/<tspan>/... subtree.
layout_object->SetSubtreeShouldDoFullPaintInvalidation(
PaintInvalidationReason::kSVGResource);
}
bool needs_layout = false;
if (ContainsResource(style.MarkerStartResource(), resource) ||
ContainsResource(style.MarkerMidResource(), resource) ||
ContainsResource(style.MarkerEndResource(), resource)) {
needs_layout = true;
layout_object->SetNeedsBoundariesUpdate();
}
const auto* clip_reference =
DynamicTo<ReferenceClipPathOperation>(style.ClipPath());
if (ContainsResource(clip_reference, resource)) {
// TODO(fs): "Downgrade" to non-subtree?
layout_object->SetSubtreeShouldDoFullPaintInvalidation();
layout_object->InvalidateClipPathCache();
}
if (ContainsResource(style.MaskerResource(), resource)) {
// TODO(fs): "Downgrade" to non-subtree?
layout_object->SetSubtreeShouldDoFullPaintInvalidation();
layout_object->SetNeedsPaintPropertyUpdate();
}
LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
*layout_object, needs_layout);
}
void SVGElementResourceClient::FilterPrimitiveChanged(
SVGResource* resource,
SVGFilterPrimitiveStandardAttributes& primitive,
const QualifiedName& attribute) {
if (filter_data_ && !filter_data_->Invalidate(primitive, attribute))
return; // No change
LayoutObject* layout_object = element_->GetLayoutObject();
if (!layout_object)
return;
layout_object->SetNeedsPaintPropertyUpdate();
MarkFilterDataDirty();
}
SVGElementResourceClient::FilterData*
SVGElementResourceClient::CreateFilterDataWithNodeMap(
FilterEffectBuilder& builder,
const ReferenceFilterOperation& reference_filter) {
auto* node_map = MakeGarbageCollected<SVGFilterGraphNodeMap>();
Filter* filter =
builder.BuildReferenceFilter(reference_filter, nullptr, node_map);
if (!filter || !filter->LastEffect())
return nullptr;
paint_filter_builder::PopulateSourceGraphicImageFilters(
filter->GetSourceGraphic(), kInterpolationSpaceSRGB);
return MakeGarbageCollected<FilterData>(filter->LastEffect(), node_map);
}
void SVGElementResourceClient::UpdateFilterData(
CompositorFilterOperations& operations) {
DCHECK(element_->GetLayoutObject());
const LayoutObject& object = *element_->GetLayoutObject();
FloatRect reference_box = SVGResources::ReferenceBoxForEffects(object);
if (!operations.IsEmpty() && !filter_data_dirty_ &&
reference_box == operations.ReferenceBox())
return;
const ComputedStyle& style = object.StyleRef();
FilterEffectBuilder builder(reference_box, 1);
builder.SetShorthandScale(1 / style.EffectiveZoom());
const FilterOperations& filter = style.Filter();
// If the filter is a single 'url(...)' reference we can optimize some
// mutations to the referenced filter chain by tracking the filter
// dependencies and only perform partial invalidations of the filter chain.
const bool is_single_reference_filter =
filter.size() == 1 && IsA<ReferenceFilterOperation>(*filter.at(0));
if (is_single_reference_filter) {
if (!filter_data_) {
filter_data_ = CreateFilterDataWithNodeMap(
builder, To<ReferenceFilterOperation>(*filter.at(0)));
}
operations.Clear();
if (filter_data_) {
operations.AppendReferenceFilter(filter_data_->BuildPaintFilter());
} else {
// Filter construction failed. Create a filter chain that yields
// transparent black.
operations.AppendOpacityFilter(0);
}
} else {
// Drop any existing filter data since the filter is no longer
// cacheable.
if (FilterData* filter_data = filter_data_.Release())
filter_data->Dispose();
operations = builder.BuildFilterOperations(filter);
}
operations.SetReferenceBox(reference_box);
filter_data_dirty_ = false;
}
void SVGElementResourceClient::InvalidateFilterData() {
// If we performed an "optimized" invalidation via FilterPrimitiveChanged(),
// we could have set |filter_data_dirty_| but not cleared |filter_data_|.
if (filter_data_dirty_ && !filter_data_)
return;
if (FilterData* filter_data = filter_data_.Release())
filter_data->Dispose();
LayoutObject* layout_object = element_->GetLayoutObject();
layout_object->SetNeedsPaintPropertyUpdate();
MarkFilterDataDirty();
}
void SVGElementResourceClient::MarkFilterDataDirty() {
DCHECK(element_->GetLayoutObject());
DCHECK(element_->GetLayoutObject()->NeedsPaintPropertyUpdate());
filter_data_dirty_ = true;
}
void SVGElementResourceClient::Trace(Visitor* visitor) const {
visitor->Trace(element_);
visitor->Trace(filter_data_);
SVGResourceClient::Trace(visitor);
}
SVGResourceInvalidator::SVGResourceInvalidator(LayoutObject& object)
: object_(object) {}
void SVGResourceInvalidator::InvalidateEffects() {
const ComputedStyle& style = object_.StyleRef();
if (style.HasFilter()) {
if (SVGElementResourceClient* client = SVGResources::GetClient(object_))
client->InvalidateFilterData();
}
if (style.HasClipPath()) {
object_.SetShouldDoFullPaintInvalidation();
object_.InvalidateClipPathCache();
}
if (style.MaskerResource()) {
object_.SetShouldDoFullPaintInvalidation();
object_.SetNeedsPaintPropertyUpdate();
}
}
void SVGResourceInvalidator::InvalidatePaints() {
SVGElementResourceClient* client = SVGResources::GetClient(object_);
if (!client)
return;
bool needs_invalidation = false;
const ComputedStyle& style = object_.StyleRef();
if (auto* fill = GetSVGResourceAsType<LayoutSVGResourcePaintServer>(
*client, style.FillPaint().Resource())) {
fill->RemoveClientFromCache(*client);
needs_invalidation = true;
}
if (auto* stroke = GetSVGResourceAsType<LayoutSVGResourcePaintServer>(
*client, style.StrokePaint().Resource())) {
stroke->RemoveClientFromCache(*client);
needs_invalidation = true;
}
if (!needs_invalidation)
return;
object_.SetSubtreeShouldDoFullPaintInvalidation(
PaintInvalidationReason::kSVGResource);
}
} // namespace blink