blob: 06642e4b669c62849bba527b91cfd2987378cba8 [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 "core/layout/svg/SVGResources.h"
#include "core/SVGNames.h"
#include "core/layout/svg/LayoutSVGResourceClipper.h"
#include "core/layout/svg/LayoutSVGResourceFilter.h"
#include "core/layout/svg/LayoutSVGResourceMarker.h"
#include "core/layout/svg/LayoutSVGResourceMasker.h"
#include "core/layout/svg/LayoutSVGResourcePaintServer.h"
#include "core/style/ComputedStyle.h"
#include "core/svg/SVGFilterElement.h"
#include "core/svg/SVGGradientElement.h"
#include "core/svg/SVGPatternElement.h"
#include "core/svg/SVGURIReference.h"
#include "wtf/PtrUtil.h"
#include <memory>
#ifndef NDEBUG
#include <stdio.h>
#endif
namespace blink {
using namespace SVGNames;
SVGResources::SVGResources() : m_linkedResource(nullptr) {}
static HashSet<AtomicString>& clipperFilterMaskerTags() {
DEFINE_STATIC_LOCAL(
HashSet<AtomicString>, s_tagList,
({
// "container elements":
// http://www.w3.org/TR/SVG11/intro.html#TermContainerElement
// "graphics elements" :
// http://www.w3.org/TR/SVG11/intro.html#TermGraphicsElement
aTag.localName(), circleTag.localName(), ellipseTag.localName(),
gTag.localName(), imageTag.localName(), lineTag.localName(),
markerTag.localName(), maskTag.localName(), pathTag.localName(),
polygonTag.localName(), polylineTag.localName(), rectTag.localName(),
svgTag.localName(), textTag.localName(), useTag.localName(),
// Not listed in the definitions is the clipPath element, the SVG spec
// says though:
// The "clipPath" element or any of its children can specify property
// "clip-path".
// So we have to add clipPathTag here, otherwhise clip-path on
// clipPath will fail. (Already mailed SVG WG, waiting for a solution)
clipPathTag.localName(),
// Not listed in the definitions are the text content elements, though
// filter/clipper/masker on tspan/text/.. is allowed.
// (Already mailed SVG WG, waiting for a solution)
textPathTag.localName(), tspanTag.localName(),
// Not listed in the definitions is the foreignObject element, but
// clip-path is a supported attribute.
foreignObjectTag.localName(),
// Elements that we ignore, as it doesn't make any sense.
// defs, pattern, switch (FIXME: Mail SVG WG about these)
// symbol (is converted to a svg element, when referenced by use, we
// can safely ignore it.)
}));
return s_tagList;
}
bool SVGResources::supportsMarkers(const SVGElement& element) {
DEFINE_STATIC_LOCAL(HashSet<AtomicString>, s_tagList,
({
lineTag.localName(), pathTag.localName(),
polygonTag.localName(), polylineTag.localName(),
}));
return s_tagList.contains(element.localName());
}
static HashSet<AtomicString>& fillAndStrokeTags() {
DEFINE_STATIC_LOCAL(
HashSet<AtomicString>, s_tagList,
({
circleTag.localName(), ellipseTag.localName(), lineTag.localName(),
pathTag.localName(), polygonTag.localName(), polylineTag.localName(),
rectTag.localName(), textTag.localName(), textPathTag.localName(),
tspanTag.localName(),
}));
return s_tagList;
}
static HashSet<AtomicString>& chainableResourceTags() {
DEFINE_STATIC_LOCAL(HashSet<AtomicString>, s_tagList,
({
linearGradientTag.localName(), filterTag.localName(),
patternTag.localName(), radialGradientTag.localName(),
}));
return s_tagList;
}
static inline AtomicString targetReferenceFromResource(SVGElement& element) {
String target;
if (isSVGPatternElement(element))
target = toSVGPatternElement(element).href()->currentValue()->value();
else if (isSVGGradientElement(element))
target = toSVGGradientElement(element).href()->currentValue()->value();
else if (isSVGFilterElement(element))
target = toSVGFilterElement(element).href()->currentValue()->value();
else
ASSERT_NOT_REACHED();
return SVGURIReference::fragmentIdentifierFromIRIString(target,
element.treeScope());
}
static inline bool svgPaintTypeHasURL(SVGPaintType paintType) {
switch (paintType) {
case SVG_PAINTTYPE_URI_NONE:
case SVG_PAINTTYPE_URI_CURRENTCOLOR:
case SVG_PAINTTYPE_URI_RGBCOLOR:
case SVG_PAINTTYPE_URI:
return true;
default:
break;
}
return false;
}
static inline LayoutSVGResourcePaintServer* paintingResourceFromSVGPaint(
TreeScope& treeScope,
const SVGPaintType& paintType,
const String& paintUri,
AtomicString& id,
bool& hasPendingResource) {
if (!svgPaintTypeHasURL(paintType))
return nullptr;
id = SVGURIReference::fragmentIdentifierFromIRIString(paintUri, treeScope);
LayoutSVGResourceContainer* container =
getLayoutSVGResourceContainerById(treeScope, id);
if (!container) {
hasPendingResource = true;
return nullptr;
}
if (!container->isSVGPaintServer())
return nullptr;
return toLayoutSVGResourcePaintServer(container);
}
static inline void registerPendingResource(SVGDocumentExtensions& extensions,
const AtomicString& id,
SVGElement* element) {
ASSERT(element);
extensions.addPendingResource(id, element);
}
bool SVGResources::hasResourceData() const {
return m_clipperFilterMaskerData || m_markerData || m_fillStrokeData ||
m_linkedResource;
}
static inline SVGResources& ensureResources(
std::unique_ptr<SVGResources>& resources) {
if (!resources)
resources = wrapUnique(new SVGResources);
return *resources.get();
}
std::unique_ptr<SVGResources> SVGResources::buildResources(
const LayoutObject* object,
const ComputedStyle& computedStyle) {
ASSERT(object);
Node* node = object->node();
ASSERT(node);
ASSERT_WITH_SECURITY_IMPLICATION(node->isSVGElement());
SVGElement* element = toSVGElement(node);
ASSERT(element);
const AtomicString& tagName = element->localName();
ASSERT(!tagName.isNull());
TreeScope& treeScope = element->treeScope();
SVGDocumentExtensions& extensions = element->document().accessSVGExtensions();
const SVGComputedStyle& style = computedStyle.svgStyle();
std::unique_ptr<SVGResources> resources;
if (clipperFilterMaskerTags().contains(tagName)) {
if (computedStyle.clipPath() && !object->isSVGRoot()) {
ClipPathOperation* clipPathOperation = computedStyle.clipPath();
if (clipPathOperation->type() == ClipPathOperation::REFERENCE) {
const ReferenceClipPathOperation& clipPathReference =
toReferenceClipPathOperation(*clipPathOperation);
AtomicString id = SVGURIReference::fragmentIdentifierFromIRIString(
clipPathReference.url(), treeScope);
if (!ensureResources(resources).setClipper(
getLayoutSVGResourceById<LayoutSVGResourceClipper>(treeScope,
id)))
registerPendingResource(extensions, id, element);
}
}
if (computedStyle.hasFilter() && !object->isSVGRoot()) {
const FilterOperations& filterOperations = computedStyle.filter();
if (filterOperations.size() == 1) {
const FilterOperation& filterOperation = *filterOperations.at(0);
if (filterOperation.type() == FilterOperation::REFERENCE) {
const auto& referenceFilterOperation =
toReferenceFilterOperation(filterOperation);
AtomicString id = SVGURIReference::fragmentIdentifierFromIRIString(
referenceFilterOperation.url(), treeScope);
if (!ensureResources(resources).setFilter(
getLayoutSVGResourceById<LayoutSVGResourceFilter>(treeScope,
id)))
registerPendingResource(extensions, id, element);
}
}
}
if (style.hasMasker()) {
AtomicString id = style.maskerResource();
if (!ensureResources(resources).setMasker(
getLayoutSVGResourceById<LayoutSVGResourceMasker>(treeScope, id)))
registerPendingResource(extensions, id, element);
}
}
if (style.hasMarkers() && supportsMarkers(*element)) {
const AtomicString& markerStartId = style.markerStartResource();
if (!ensureResources(resources).setMarkerStart(
getLayoutSVGResourceById<LayoutSVGResourceMarker>(treeScope,
markerStartId)))
registerPendingResource(extensions, markerStartId, element);
const AtomicString& markerMidId = style.markerMidResource();
if (!ensureResources(resources).setMarkerMid(
getLayoutSVGResourceById<LayoutSVGResourceMarker>(treeScope,
markerMidId)))
registerPendingResource(extensions, markerMidId, element);
const AtomicString& markerEndId = style.markerEndResource();
if (!ensureResources(resources).setMarkerEnd(
getLayoutSVGResourceById<LayoutSVGResourceMarker>(
treeScope, style.markerEndResource())))
registerPendingResource(extensions, markerEndId, element);
}
if (fillAndStrokeTags().contains(tagName)) {
if (style.hasFill()) {
bool hasPendingResource = false;
AtomicString id;
LayoutSVGResourcePaintServer* resource = paintingResourceFromSVGPaint(
treeScope, style.fillPaintType(), style.fillPaintUri(), id,
hasPendingResource);
if (!ensureResources(resources).setFill(resource) && hasPendingResource)
registerPendingResource(extensions, id, element);
}
if (style.hasStroke()) {
bool hasPendingResource = false;
AtomicString id;
LayoutSVGResourcePaintServer* resource = paintingResourceFromSVGPaint(
treeScope, style.strokePaintType(), style.strokePaintUri(), id,
hasPendingResource);
if (!ensureResources(resources).setStroke(resource) && hasPendingResource)
registerPendingResource(extensions, id, element);
}
}
if (chainableResourceTags().contains(tagName)) {
AtomicString id = targetReferenceFromResource(*element);
if (!ensureResources(resources).setLinkedResource(
getLayoutSVGResourceContainerById(treeScope, id)))
registerPendingResource(extensions, id, element);
}
return (!resources || !resources->hasResourceData()) ? nullptr
: std::move(resources);
}
void SVGResources::layoutIfNeeded() {
if (m_clipperFilterMaskerData) {
if (LayoutSVGResourceClipper* clipper = m_clipperFilterMaskerData->clipper)
clipper->layoutIfNeeded();
if (LayoutSVGResourceMasker* masker = m_clipperFilterMaskerData->masker)
masker->layoutIfNeeded();
if (LayoutSVGResourceFilter* filter = m_clipperFilterMaskerData->filter)
filter->layoutIfNeeded();
}
if (m_markerData) {
if (LayoutSVGResourceMarker* marker = m_markerData->markerStart)
marker->layoutIfNeeded();
if (LayoutSVGResourceMarker* marker = m_markerData->markerMid)
marker->layoutIfNeeded();
if (LayoutSVGResourceMarker* marker = m_markerData->markerEnd)
marker->layoutIfNeeded();
}
if (m_fillStrokeData) {
if (LayoutSVGResourcePaintServer* fill = m_fillStrokeData->fill)
fill->layoutIfNeeded();
if (LayoutSVGResourcePaintServer* stroke = m_fillStrokeData->stroke)
stroke->layoutIfNeeded();
}
if (m_linkedResource)
m_linkedResource->layoutIfNeeded();
}
void SVGResources::removeClientFromCache(LayoutObject* object,
bool markForInvalidation) const {
if (!hasResourceData())
return;
if (m_linkedResource) {
ASSERT(!m_clipperFilterMaskerData);
ASSERT(!m_markerData);
ASSERT(!m_fillStrokeData);
m_linkedResource->removeClientFromCache(object, markForInvalidation);
return;
}
if (m_clipperFilterMaskerData) {
if (m_clipperFilterMaskerData->clipper)
m_clipperFilterMaskerData->clipper->removeClientFromCache(
object, markForInvalidation);
if (m_clipperFilterMaskerData->filter)
m_clipperFilterMaskerData->filter->removeClientFromCache(
object, markForInvalidation);
if (m_clipperFilterMaskerData->masker)
m_clipperFilterMaskerData->masker->removeClientFromCache(
object, markForInvalidation);
}
if (m_markerData) {
if (m_markerData->markerStart)
m_markerData->markerStart->removeClientFromCache(object,
markForInvalidation);
if (m_markerData->markerMid)
m_markerData->markerMid->removeClientFromCache(object,
markForInvalidation);
if (m_markerData->markerEnd)
m_markerData->markerEnd->removeClientFromCache(object,
markForInvalidation);
}
if (m_fillStrokeData) {
if (m_fillStrokeData->fill)
m_fillStrokeData->fill->removeClientFromCache(object,
markForInvalidation);
if (m_fillStrokeData->stroke)
m_fillStrokeData->stroke->removeClientFromCache(object,
markForInvalidation);
}
}
void SVGResources::resourceDestroyed(LayoutSVGResourceContainer* resource) {
ASSERT(resource);
if (!hasResourceData())
return;
if (m_linkedResource == resource) {
ASSERT(!m_clipperFilterMaskerData);
ASSERT(!m_markerData);
ASSERT(!m_fillStrokeData);
m_linkedResource->removeAllClientsFromCache();
m_linkedResource = nullptr;
return;
}
switch (resource->resourceType()) {
case MaskerResourceType:
if (!m_clipperFilterMaskerData)
break;
if (m_clipperFilterMaskerData->masker == resource)
m_clipperFilterMaskerData->masker = nullptr;
break;
case MarkerResourceType:
if (!m_markerData)
break;
if (m_markerData->markerStart == resource)
m_markerData->markerStart = nullptr;
if (m_markerData->markerMid == resource)
m_markerData->markerMid = nullptr;
if (m_markerData->markerEnd == resource)
m_markerData->markerEnd = nullptr;
break;
case PatternResourceType:
case LinearGradientResourceType:
case RadialGradientResourceType:
if (!m_fillStrokeData)
break;
if (m_fillStrokeData->fill == resource)
m_fillStrokeData->fill = nullptr;
if (m_fillStrokeData->stroke == resource)
m_fillStrokeData->stroke = nullptr;
break;
case FilterResourceType:
if (!m_clipperFilterMaskerData)
break;
if (m_clipperFilterMaskerData->filter == resource)
m_clipperFilterMaskerData->filter = nullptr;
break;
case ClipperResourceType:
if (!m_clipperFilterMaskerData)
break;
if (m_clipperFilterMaskerData->clipper == resource)
m_clipperFilterMaskerData->clipper = nullptr;
break;
default:
ASSERT_NOT_REACHED();
}
}
void SVGResources::buildSetOfResources(
HashSet<LayoutSVGResourceContainer*>& set) {
if (!hasResourceData())
return;
if (m_linkedResource) {
ASSERT(!m_clipperFilterMaskerData);
ASSERT(!m_markerData);
ASSERT(!m_fillStrokeData);
set.add(m_linkedResource);
return;
}
if (m_clipperFilterMaskerData) {
if (m_clipperFilterMaskerData->clipper)
set.add(m_clipperFilterMaskerData->clipper);
if (m_clipperFilterMaskerData->filter)
set.add(m_clipperFilterMaskerData->filter);
if (m_clipperFilterMaskerData->masker)
set.add(m_clipperFilterMaskerData->masker);
}
if (m_markerData) {
if (m_markerData->markerStart)
set.add(m_markerData->markerStart);
if (m_markerData->markerMid)
set.add(m_markerData->markerMid);
if (m_markerData->markerEnd)
set.add(m_markerData->markerEnd);
}
if (m_fillStrokeData) {
if (m_fillStrokeData->fill)
set.add(m_fillStrokeData->fill);
if (m_fillStrokeData->stroke)
set.add(m_fillStrokeData->stroke);
}
}
bool SVGResources::setClipper(LayoutSVGResourceClipper* clipper) {
if (!clipper)
return false;
ASSERT(clipper->resourceType() == ClipperResourceType);
if (!m_clipperFilterMaskerData)
m_clipperFilterMaskerData = ClipperFilterMaskerData::create();
m_clipperFilterMaskerData->clipper = clipper;
return true;
}
void SVGResources::resetClipper() {
ASSERT(m_clipperFilterMaskerData);
ASSERT(m_clipperFilterMaskerData->clipper);
m_clipperFilterMaskerData->clipper = nullptr;
}
bool SVGResources::setFilter(LayoutSVGResourceFilter* filter) {
if (!filter)
return false;
ASSERT(filter->resourceType() == FilterResourceType);
if (!m_clipperFilterMaskerData)
m_clipperFilterMaskerData = ClipperFilterMaskerData::create();
m_clipperFilterMaskerData->filter = filter;
return true;
}
void SVGResources::resetFilter() {
ASSERT(m_clipperFilterMaskerData);
ASSERT(m_clipperFilterMaskerData->filter);
m_clipperFilterMaskerData->filter = nullptr;
}
bool SVGResources::setMarkerStart(LayoutSVGResourceMarker* markerStart) {
if (!markerStart)
return false;
ASSERT(markerStart->resourceType() == MarkerResourceType);
if (!m_markerData)
m_markerData = MarkerData::create();
m_markerData->markerStart = markerStart;
return true;
}
void SVGResources::resetMarkerStart() {
ASSERT(m_markerData);
ASSERT(m_markerData->markerStart);
m_markerData->markerStart = nullptr;
}
bool SVGResources::setMarkerMid(LayoutSVGResourceMarker* markerMid) {
if (!markerMid)
return false;
ASSERT(markerMid->resourceType() == MarkerResourceType);
if (!m_markerData)
m_markerData = MarkerData::create();
m_markerData->markerMid = markerMid;
return true;
}
void SVGResources::resetMarkerMid() {
ASSERT(m_markerData);
ASSERT(m_markerData->markerMid);
m_markerData->markerMid = nullptr;
}
bool SVGResources::setMarkerEnd(LayoutSVGResourceMarker* markerEnd) {
if (!markerEnd)
return false;
ASSERT(markerEnd->resourceType() == MarkerResourceType);
if (!m_markerData)
m_markerData = MarkerData::create();
m_markerData->markerEnd = markerEnd;
return true;
}
void SVGResources::resetMarkerEnd() {
ASSERT(m_markerData);
ASSERT(m_markerData->markerEnd);
m_markerData->markerEnd = nullptr;
}
bool SVGResources::setMasker(LayoutSVGResourceMasker* masker) {
if (!masker)
return false;
ASSERT(masker->resourceType() == MaskerResourceType);
if (!m_clipperFilterMaskerData)
m_clipperFilterMaskerData = ClipperFilterMaskerData::create();
m_clipperFilterMaskerData->masker = masker;
return true;
}
void SVGResources::resetMasker() {
ASSERT(m_clipperFilterMaskerData);
ASSERT(m_clipperFilterMaskerData->masker);
m_clipperFilterMaskerData->masker = nullptr;
}
bool SVGResources::setFill(LayoutSVGResourcePaintServer* fill) {
if (!fill)
return false;
if (!m_fillStrokeData)
m_fillStrokeData = FillStrokeData::create();
m_fillStrokeData->fill = fill;
return true;
}
void SVGResources::resetFill() {
ASSERT(m_fillStrokeData);
ASSERT(m_fillStrokeData->fill);
m_fillStrokeData->fill = nullptr;
}
bool SVGResources::setStroke(LayoutSVGResourcePaintServer* stroke) {
if (!stroke)
return false;
if (!m_fillStrokeData)
m_fillStrokeData = FillStrokeData::create();
m_fillStrokeData->stroke = stroke;
return true;
}
void SVGResources::resetStroke() {
ASSERT(m_fillStrokeData);
ASSERT(m_fillStrokeData->stroke);
m_fillStrokeData->stroke = nullptr;
}
bool SVGResources::setLinkedResource(
LayoutSVGResourceContainer* linkedResource) {
if (!linkedResource)
return false;
m_linkedResource = linkedResource;
return true;
}
void SVGResources::resetLinkedResource() {
ASSERT(m_linkedResource);
m_linkedResource = nullptr;
}
#ifndef NDEBUG
void SVGResources::dump(const LayoutObject* object) {
ASSERT(object);
ASSERT(object->node());
fprintf(stderr, "-> this=%p, SVGResources(layoutObject=%p, node=%p)\n", this,
object, object->node());
fprintf(stderr, " | DOM Tree:\n");
fprintf(stderr, "%s", object->node()->toTreeStringForThis().utf8().data());
fprintf(stderr, "\n | List of resources:\n");
if (m_clipperFilterMaskerData) {
if (LayoutSVGResourceClipper* clipper = m_clipperFilterMaskerData->clipper)
fprintf(stderr, " |-> Clipper : %p (node=%p)\n", clipper,
clipper->element());
if (LayoutSVGResourceFilter* filter = m_clipperFilterMaskerData->filter)
fprintf(stderr, " |-> Filter : %p (node=%p)\n", filter,
filter->element());
if (LayoutSVGResourceMasker* masker = m_clipperFilterMaskerData->masker)
fprintf(stderr, " |-> Masker : %p (node=%p)\n", masker,
masker->element());
}
if (m_markerData) {
if (LayoutSVGResourceMarker* markerStart = m_markerData->markerStart)
fprintf(stderr, " |-> MarkerStart: %p (node=%p)\n", markerStart,
markerStart->element());
if (LayoutSVGResourceMarker* markerMid = m_markerData->markerMid)
fprintf(stderr, " |-> MarkerMid : %p (node=%p)\n", markerMid,
markerMid->element());
if (LayoutSVGResourceMarker* markerEnd = m_markerData->markerEnd)
fprintf(stderr, " |-> MarkerEnd : %p (node=%p)\n", markerEnd,
markerEnd->element());
}
if (m_fillStrokeData) {
if (LayoutSVGResourcePaintServer* fill = m_fillStrokeData->fill)
fprintf(stderr, " |-> Fill : %p (node=%p)\n", fill,
fill->element());
if (LayoutSVGResourcePaintServer* stroke = m_fillStrokeData->stroke)
fprintf(stderr, " |-> Stroke : %p (node=%p)\n", stroke,
stroke->element());
}
if (m_linkedResource)
fprintf(stderr, " |-> xlink:href : %p (node=%p)\n", m_linkedResource,
m_linkedResource->element());
}
#endif
} // namespace blink