blob: 5f88565008bcf7d6dc8d590e0703680303f14407 [file] [log] [blame]
/*
* Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
* Copyright (C) 2021-2022 Apple Inc. 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 "config.h"
#include "SVGFilterBuilder.h"
#include "ElementIterator.h"
#include "SVGFilterElement.h"
#include "SVGFilterPrimitiveStandardAttributes.h"
#include "SourceAlpha.h"
#include "SourceGraphic.h"
#if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB)
#include "CSSComputedStyleDeclaration.h"
#include "CSSPrimitiveValueMappings.h"
#endif
namespace WebCore {
static constexpr unsigned maxTotalNumberFilterEffects = 100;
static constexpr unsigned maxCountChildNodes = 200;
void SVGFilterBuilder::setupBuiltinEffects(Ref<FilterEffect> sourceGraphic)
{
m_builtinEffects.add(SourceGraphic::effectName(), sourceGraphic);
m_builtinEffects.add(SourceAlpha::effectName(), SourceAlpha::create(sourceGraphic->operatingColorSpace()));
}
static OptionSet<FilterEffectGeometry::Flags> effectGeometryFlagsForElement(SVGElement& element)
{
OptionSet<FilterEffectGeometry::Flags> flags;
if (element.hasAttribute(SVGNames::xAttr))
flags.add(FilterEffectGeometry::Flags::HasX);
if (element.hasAttribute(SVGNames::yAttr))
flags.add(FilterEffectGeometry::Flags::HasY);
if (element.hasAttribute(SVGNames::widthAttr))
flags.add(FilterEffectGeometry::Flags::HasWidth);
if (element.hasAttribute(SVGNames::heightAttr))
flags.add(FilterEffectGeometry::Flags::HasHeight);
return flags;
}
#if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB)
static ColorInterpolation colorInterpolationForElement(SVGElement& element)
{
if (auto renderer = element.renderer())
return renderer->style().svgStyle().colorInterpolationFilters();
// Try to determine the property value from the computed style.
if (auto value = ComputedStyleExtractor(&element).propertyValue(CSSPropertyColorInterpolationFilters, DoNotUpdateLayout)) {
if (is<CSSPrimitiveValue>(value))
return downcast<CSSPrimitiveValue>(*value);
}
return ColorInterpolation::Auto;
}
#endif
RefPtr<FilterEffect> SVGFilterBuilder::buildFilterEffects(SVGFilterElement& filterElement)
{
if (filterElement.countChildNodes() > maxCountChildNodes)
return nullptr;
setEffectInputs(sourceAlpha(), FilterEffectVector { sourceGraphic() });
for (auto& effectElement : childrenOfType<SVGFilterPrimitiveStandardAttributes>(filterElement)) {
auto inputs = namedEffects(effectElement.filterEffectInputsNames());
if (!inputs)
return nullptr;
auto effect = effectElement.filterEffect(*this, *inputs);
if (!effect)
return nullptr;
if (auto flags = effectGeometryFlagsForElement(effectElement)) {
auto effectBoundaries = SVGLengthContext::resolveRectangle<SVGFilterPrimitiveStandardAttributes>(&effectElement, m_primitiveUnits, m_targetBoundingBox);
m_effectGeometryMap.add(*effect, FilterEffectGeometry(effectBoundaries, flags));
}
#if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB)
if (colorInterpolationForElement(effectElement) == ColorInterpolation::LinearRGB)
effect->setOperatingColorSpace(DestinationColorSpace::LinearSRGB());
#endif
if (auto renderer = effectElement.renderer())
appendEffectToEffectRenderer(*effect, *renderer);
addNamedEffect(effectElement.result(), { *effect });
setEffectInputs(*effect, WTFMove(*inputs));
}
return m_lastEffect;
}
FilterEffect& SVGFilterBuilder::sourceGraphic() const
{
return *m_builtinEffects.get(FilterEffect::sourceGraphicName());
}
FilterEffect& SVGFilterBuilder::sourceAlpha() const
{
return *m_builtinEffects.get(FilterEffect::sourceAlphaName());
}
void SVGFilterBuilder::addNamedEffect(const AtomString& id, Ref<FilterEffect>&& effect)
{
if (id.isEmpty()) {
m_lastEffect = WTFMove(effect);
return;
}
if (m_builtinEffects.contains(id))
return;
m_lastEffect = WTFMove(effect);
m_namedEffects.set(id, Ref { *m_lastEffect });
}
RefPtr<FilterEffect> SVGFilterBuilder::namedEffect(const AtomString& id) const
{
if (id.isEmpty()) {
if (m_lastEffect)
return m_lastEffect;
return &sourceGraphic();
}
if (m_builtinEffects.contains(id))
return m_builtinEffects.get(id);
return m_namedEffects.get(id);
}
std::optional<FilterEffectVector> SVGFilterBuilder::namedEffects(Span<const AtomString> names) const
{
FilterEffectVector effects;
effects.reserveInitialCapacity(names.size());
for (auto& name : names) {
auto effect = namedEffect(name);
if (!effect)
return std::nullopt;
effects.uncheckedAppend(effect.releaseNonNull());
}
return effects;
}
void SVGFilterBuilder::setEffectInputs(FilterEffect& effect, FilterEffectVector&& inputs)
{
m_inputsMap.set({ effect }, WTFMove(inputs));
}
void SVGFilterBuilder::appendEffectToEffectRenderer(FilterEffect& effect, RenderObject& object)
{
m_effectRenderer.add(&object, &effect);
}
std::optional<FilterEffectGeometry> SVGFilterBuilder::effectGeometry(FilterEffect& effect) const
{
auto it = m_effectGeometryMap.find(effect);
if (it != m_effectGeometryMap.end())
return it->value;
return std::nullopt;
}
bool SVGFilterBuilder::buildEffectExpression(FilterEffect& effect, FilterEffectVector& stack, unsigned level, SVGFilterExpression& expression) const
{
// A cycle is detected.
if (stack.contains(effect))
return false;
stack.append(effect);
expression.append({ effect, effectGeometry(effect), level });
for (auto& input : m_inputsMap.get(effect)) {
if (!buildEffectExpression(input, stack, level + 1, expression))
return false;
}
ASSERT(!stack.isEmpty());
ASSERT(stack.last() == effect);
stack.removeLast();
return true;
}
bool SVGFilterBuilder::buildExpression(SVGFilterExpression& expression) const
{
if (!m_lastEffect)
return false;
FilterEffectVector stack;
if (!buildEffectExpression(*m_lastEffect, stack, 0, expression))
return false;
if (expression.size() > maxTotalNumberFilterEffects)
return false;
expression.reverse();
expression.shrinkToFit();
return true;
}
} // namespace WebCore