1/*
2 * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2010 Rob Buis <rwlbuis@gmail.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB.  If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22
23#if ENABLE(SVG)
24#include "SVGTextPathElement.h"
25
26#include "Attribute.h"
27#include "NodeRenderingContext.h"
28#include "RenderSVGResource.h"
29#include "RenderSVGTextPath.h"
30#include "SVGElementInstance.h"
31#include "SVGNames.h"
32
33namespace WebCore {
34
35// Animated property definitions
36DEFINE_ANIMATED_LENGTH(SVGTextPathElement, SVGNames::startOffsetAttr, StartOffset, startOffset)
37DEFINE_ANIMATED_ENUMERATION(SVGTextPathElement, SVGNames::methodAttr, Method, method, SVGTextPathMethodType)
38DEFINE_ANIMATED_ENUMERATION(SVGTextPathElement, SVGNames::spacingAttr, Spacing, spacing, SVGTextPathSpacingType)
39DEFINE_ANIMATED_STRING(SVGTextPathElement, XLinkNames::hrefAttr, Href, href)
40
41BEGIN_REGISTER_ANIMATED_PROPERTIES(SVGTextPathElement)
42    REGISTER_LOCAL_ANIMATED_PROPERTY(startOffset)
43    REGISTER_LOCAL_ANIMATED_PROPERTY(method)
44    REGISTER_LOCAL_ANIMATED_PROPERTY(spacing)
45    REGISTER_LOCAL_ANIMATED_PROPERTY(href)
46    REGISTER_PARENT_ANIMATED_PROPERTIES(SVGTextContentElement)
47END_REGISTER_ANIMATED_PROPERTIES
48
49inline SVGTextPathElement::SVGTextPathElement(const QualifiedName& tagName, Document* document)
50    : SVGTextContentElement(tagName, document)
51    , m_startOffset(LengthModeOther)
52    , m_method(SVGTextPathMethodAlign)
53    , m_spacing(SVGTextPathSpacingExact)
54{
55    ASSERT(hasTagName(SVGNames::textPathTag));
56    registerAnimatedPropertiesForSVGTextPathElement();
57}
58
59PassRefPtr<SVGTextPathElement> SVGTextPathElement::create(const QualifiedName& tagName, Document* document)
60{
61    return adoptRef(new SVGTextPathElement(tagName, document));
62}
63
64SVGTextPathElement::~SVGTextPathElement()
65{
66    clearResourceReferences();
67}
68
69void SVGTextPathElement::clearResourceReferences()
70{
71    ASSERT(document());
72    document()->accessSVGExtensions()->removeAllTargetReferencesForElement(this);
73}
74
75bool SVGTextPathElement::isSupportedAttribute(const QualifiedName& attrName)
76{
77    DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, supportedAttributes, ());
78    if (supportedAttributes.isEmpty()) {
79        SVGURIReference::addSupportedAttributes(supportedAttributes);
80        supportedAttributes.add(SVGNames::startOffsetAttr);
81        supportedAttributes.add(SVGNames::methodAttr);
82        supportedAttributes.add(SVGNames::spacingAttr);
83    }
84    return supportedAttributes.contains<QualifiedName, SVGAttributeHashTranslator>(attrName);
85}
86
87void SVGTextPathElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
88{
89    SVGParsingError parseError = NoError;
90
91    if (!isSupportedAttribute(name))
92        SVGTextContentElement::parseAttribute(name, value);
93    else if (name == SVGNames::startOffsetAttr)
94        setStartOffsetBaseValue(SVGLength::construct(LengthModeOther, value, parseError));
95    else if (name == SVGNames::methodAttr) {
96        SVGTextPathMethodType propertyValue = SVGPropertyTraits<SVGTextPathMethodType>::fromString(value);
97        if (propertyValue > 0)
98            setMethodBaseValue(propertyValue);
99    } else if (name == SVGNames::spacingAttr) {
100        SVGTextPathSpacingType propertyValue = SVGPropertyTraits<SVGTextPathSpacingType>::fromString(value);
101        if (propertyValue > 0)
102            setSpacingBaseValue(propertyValue);
103    } else if (SVGURIReference::parseAttribute(name, value)) {
104    } else
105        ASSERT_NOT_REACHED();
106
107    reportAttributeParsingError(parseError, name, value);
108}
109
110void SVGTextPathElement::svgAttributeChanged(const QualifiedName& attrName)
111{
112    if (!isSupportedAttribute(attrName)) {
113        SVGTextContentElement::svgAttributeChanged(attrName);
114        return;
115    }
116
117    SVGElementInstance::InvalidationGuard invalidationGuard(this);
118
119    if (SVGURIReference::isKnownAttribute(attrName)) {
120        buildPendingResource();
121        return;
122    }
123
124    if (attrName == SVGNames::startOffsetAttr)
125        updateRelativeLengthsInformation();
126
127    if (RenderObject* object = renderer())
128        RenderSVGResource::markForLayoutAndParentResourceInvalidation(object);
129}
130
131RenderObject* SVGTextPathElement::createRenderer(RenderArena* arena, RenderStyle*)
132{
133    return new (arena) RenderSVGTextPath(this);
134}
135
136bool SVGTextPathElement::childShouldCreateRenderer(const NodeRenderingContext& childContext) const
137{
138    if (childContext.node()->isTextNode()
139        || childContext.node()->hasTagName(SVGNames::aTag)
140        || childContext.node()->hasTagName(SVGNames::trefTag)
141        || childContext.node()->hasTagName(SVGNames::tspanTag))
142        return true;
143
144    return false;
145}
146
147bool SVGTextPathElement::rendererIsNeeded(const NodeRenderingContext& context)
148{
149    if (parentNode()
150        && (parentNode()->hasTagName(SVGNames::aTag)
151            || parentNode()->hasTagName(SVGNames::textTag)))
152        return StyledElement::rendererIsNeeded(context);
153
154    return false;
155}
156
157void SVGTextPathElement::buildPendingResource()
158{
159    clearResourceReferences();
160    if (!inDocument())
161        return;
162
163    String id;
164    Element* target = SVGURIReference::targetElementFromIRIString(href(), document(), &id);
165    if (!target) {
166        // Do not register as pending if we are already pending this resource.
167        if (document()->accessSVGExtensions()->isElementPendingResource(this, id))
168            return;
169
170        if (!id.isEmpty()) {
171            document()->accessSVGExtensions()->addPendingResource(id, this);
172            ASSERT(hasPendingResources());
173        }
174    } else if (target->hasTagName(SVGNames::pathTag)) {
175        // Register us with the target in the dependencies map. Any change of hrefElement
176        // that leads to relayout/repainting now informs us, so we can react to it.
177        document()->accessSVGExtensions()->addElementReferencingTarget(this, toSVGElement(target));
178    }
179}
180
181Node::InsertionNotificationRequest SVGTextPathElement::insertedInto(ContainerNode* rootParent)
182{
183    SVGTextContentElement::insertedInto(rootParent);
184    return InsertionShouldCallDidNotifySubtreeInsertions;
185}
186
187void SVGTextPathElement::didNotifySubtreeInsertions(ContainerNode*)
188{
189    buildPendingResource();
190}
191
192void SVGTextPathElement::removedFrom(ContainerNode* rootParent)
193{
194    SVGTextContentElement::removedFrom(rootParent);
195    if (rootParent->inDocument())
196        clearResourceReferences();
197}
198
199bool SVGTextPathElement::selfHasRelativeLengths() const
200{
201    return startOffset().isRelative()
202        || SVGTextContentElement::selfHasRelativeLengths();
203}
204
205}
206
207#endif // ENABLE(SVG)
208