1/*
2 * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2010 Rob Buis <rwlbuis@gmail.com>
4 * Copyright (C) 2018-2019 Apple Inc. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23#include "SVGTextPathElement.h"
24
25#include "RenderSVGResource.h"
26#include "RenderSVGTextPath.h"
27#include "SVGDocumentExtensions.h"
28#include "SVGNames.h"
29#include <wtf/IsoMallocInlines.h>
30#include <wtf/NeverDestroyed.h>
31
32namespace WebCore {
33
34WTF_MAKE_ISO_ALLOCATED_IMPL(SVGTextPathElement);
35
36inline SVGTextPathElement::SVGTextPathElement(const QualifiedName& tagName, Document& document)
37 : SVGTextContentElement(tagName, document)
38 , SVGURIReference(this)
39{
40 ASSERT(hasTagName(SVGNames::textPathTag));
41
42 static std::once_flag onceFlag;
43 std::call_once(onceFlag, [] {
44 PropertyRegistry::registerProperty<SVGNames::startOffsetAttr, &SVGTextPathElement::m_startOffset>();
45 PropertyRegistry::registerProperty<SVGNames::methodAttr, SVGTextPathMethodType, &SVGTextPathElement::m_method>();
46 PropertyRegistry::registerProperty<SVGNames::spacingAttr, SVGTextPathSpacingType, &SVGTextPathElement::m_spacing>();
47 });
48}
49
50Ref<SVGTextPathElement> SVGTextPathElement::create(const QualifiedName& tagName, Document& document)
51{
52 return adoptRef(*new SVGTextPathElement(tagName, document));
53}
54
55SVGTextPathElement::~SVGTextPathElement()
56{
57 clearResourceReferences();
58}
59
60void SVGTextPathElement::clearResourceReferences()
61{
62 document().accessSVGExtensions().removeAllTargetReferencesForElement(*this);
63}
64
65void SVGTextPathElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
66{
67 SVGParsingError parseError = NoError;
68
69 if (name == SVGNames::startOffsetAttr)
70 m_startOffset->setBaseValInternal(SVGLengthValue::construct(LengthModeOther, value, parseError));
71 else if (name == SVGNames::methodAttr) {
72 SVGTextPathMethodType propertyValue = SVGPropertyTraits<SVGTextPathMethodType>::fromString(value);
73 if (propertyValue > 0)
74 m_method->setBaseValInternal<SVGTextPathMethodType>(propertyValue);
75 } else if (name == SVGNames::spacingAttr) {
76 SVGTextPathSpacingType propertyValue = SVGPropertyTraits<SVGTextPathSpacingType>::fromString(value);
77 if (propertyValue > 0)
78 m_spacing->setBaseValInternal<SVGTextPathSpacingType>(propertyValue);
79 }
80
81 reportAttributeParsingError(parseError, name, value);
82
83 SVGTextContentElement::parseAttribute(name, value);
84 SVGURIReference::parseAttribute(name, value);
85}
86
87void SVGTextPathElement::svgAttributeChanged(const QualifiedName& attrName)
88{
89 if (PropertyRegistry::isKnownAttribute(attrName)) {
90 InstanceInvalidationGuard guard(*this);
91
92 if (attrName == SVGNames::startOffsetAttr)
93 updateRelativeLengthsInformation();
94
95 if (auto renderer = this->renderer())
96 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
97 return;
98 }
99
100 if (SVGURIReference::isKnownAttribute(attrName)) {
101 buildPendingResource();
102 if (auto renderer = this->renderer())
103 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
104 return;
105 }
106
107 SVGTextContentElement::svgAttributeChanged(attrName);
108}
109
110RenderPtr<RenderElement> SVGTextPathElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
111{
112 return createRenderer<RenderSVGTextPath>(*this, WTFMove(style));
113}
114
115bool SVGTextPathElement::childShouldCreateRenderer(const Node& child) const
116{
117 if (child.isTextNode()
118 || child.hasTagName(SVGNames::aTag)
119 || child.hasTagName(SVGNames::trefTag)
120 || child.hasTagName(SVGNames::tspanTag))
121 return true;
122
123 return false;
124}
125
126bool SVGTextPathElement::rendererIsNeeded(const RenderStyle& style)
127{
128 if (parentNode()
129 && (parentNode()->hasTagName(SVGNames::aTag)
130 || parentNode()->hasTagName(SVGNames::textTag)))
131 return StyledElement::rendererIsNeeded(style);
132
133 return false;
134}
135
136void SVGTextPathElement::buildPendingResource()
137{
138 clearResourceReferences();
139 if (!isConnected())
140 return;
141
142 auto target = SVGURIReference::targetElementFromIRIString(href(), treeScope());
143 if (!target.element) {
144 // Do not register as pending if we are already pending this resource.
145 if (document().accessSVGExtensions().isPendingResource(*this, target.identifier))
146 return;
147
148 if (!target.identifier.isEmpty()) {
149 document().accessSVGExtensions().addPendingResource(target.identifier, *this);
150 ASSERT(hasPendingResources());
151 }
152 } else if (target.element->hasTagName(SVGNames::pathTag)) {
153 // Register us with the target in the dependencies map. Any change of hrefElement
154 // that leads to relayout/repainting now informs us, so we can react to it.
155 document().accessSVGExtensions().addElementReferencingTarget(*this, downcast<SVGElement>(*target.element));
156 }
157}
158
159Node::InsertedIntoAncestorResult SVGTextPathElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
160{
161 SVGTextContentElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
162 return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
163}
164
165void SVGTextPathElement::didFinishInsertingNode()
166{
167 buildPendingResource();
168}
169
170void SVGTextPathElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
171{
172 SVGTextContentElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
173 if (removalType.disconnectedFromDocument)
174 clearResourceReferences();
175}
176
177bool SVGTextPathElement::selfHasRelativeLengths() const
178{
179 return startOffset().isRelative()
180 || SVGTextContentElement::selfHasRelativeLengths();
181}
182
183}
184