1/*
2 * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org>
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#include "SVGTextContentElement.h"
23
24#include "CSSPropertyNames.h"
25#include "CSSValueKeywords.h"
26#include "Frame.h"
27#include "FrameSelection.h"
28#include "RenderObject.h"
29#include "RenderSVGResource.h"
30#include "RenderSVGText.h"
31#include "SVGDocumentExtensions.h"
32#include "SVGElementInstance.h"
33#include "SVGNames.h"
34#include "SVGTextQuery.h"
35#include "XMLNames.h"
36#include <wtf/NeverDestroyed.h>
37
38namespace WebCore {
39
40// Define custom animated property 'textLength'.
41const SVGPropertyInfo* SVGTextContentElement::textLengthPropertyInfo()
42{
43    static const SVGPropertyInfo* s_propertyInfo = 0;
44    if (!s_propertyInfo) {
45        s_propertyInfo = new SVGPropertyInfo(AnimatedLength,
46                                             PropertyIsReadWrite,
47                                             SVGNames::textLengthAttr,
48                                             SVGNames::textLengthAttr.localName(),
49                                             &SVGTextContentElement::synchronizeTextLength,
50                                             &SVGTextContentElement::lookupOrCreateTextLengthWrapper);
51    }
52    return s_propertyInfo;
53}
54
55// Animated property definitions
56DEFINE_ANIMATED_ENUMERATION(SVGTextContentElement, SVGNames::lengthAdjustAttr, LengthAdjust, lengthAdjust, SVGLengthAdjustType)
57DEFINE_ANIMATED_BOOLEAN(SVGTextContentElement, SVGNames::externalResourcesRequiredAttr, ExternalResourcesRequired, externalResourcesRequired)
58
59BEGIN_REGISTER_ANIMATED_PROPERTIES(SVGTextContentElement)
60    REGISTER_LOCAL_ANIMATED_PROPERTY(textLength)
61    REGISTER_LOCAL_ANIMATED_PROPERTY(lengthAdjust)
62    REGISTER_LOCAL_ANIMATED_PROPERTY(externalResourcesRequired)
63    REGISTER_PARENT_ANIMATED_PROPERTIES(SVGGraphicsElement)
64END_REGISTER_ANIMATED_PROPERTIES
65
66SVGTextContentElement::SVGTextContentElement(const QualifiedName& tagName, Document& document)
67    : SVGGraphicsElement(tagName, document)
68    , m_textLength(LengthModeOther)
69    , m_specifiedTextLength(LengthModeOther)
70    , m_lengthAdjust(SVGLengthAdjustSpacing)
71{
72    registerAnimatedPropertiesForSVGTextContentElement();
73}
74
75void SVGTextContentElement::synchronizeTextLength(SVGElement* contextElement)
76{
77    ASSERT(contextElement);
78    SVGTextContentElement* ownerType = toSVGTextContentElement(contextElement);
79    if (!ownerType->m_textLength.shouldSynchronize)
80        return;
81    AtomicString value(SVGPropertyTraits<SVGLength>::toString(ownerType->m_specifiedTextLength));
82    ownerType->m_textLength.synchronize(ownerType, textLengthPropertyInfo()->attributeName, value);
83}
84
85PassRefPtr<SVGAnimatedProperty> SVGTextContentElement::lookupOrCreateTextLengthWrapper(SVGElement* contextElement)
86{
87    ASSERT(contextElement);
88    SVGTextContentElement* ownerType = toSVGTextContentElement(contextElement);
89    return SVGAnimatedProperty::lookupOrCreateWrapper<SVGTextContentElement, SVGAnimatedLength, SVGLength>
90           (ownerType, textLengthPropertyInfo(), ownerType->m_textLength.value);
91}
92
93PassRefPtr<SVGAnimatedLength> SVGTextContentElement::textLengthAnimated()
94{
95    static NeverDestroyed<SVGLength> defaultTextLength(LengthModeOther);
96    if (m_specifiedTextLength == defaultTextLength)
97        m_textLength.value.newValueSpecifiedUnits(LengthTypeNumber, getComputedTextLength(), ASSERT_NO_EXCEPTION);
98
99    m_textLength.shouldSynchronize = true;
100    return static_pointer_cast<SVGAnimatedLength>(lookupOrCreateTextLengthWrapper(this));
101
102}
103
104unsigned SVGTextContentElement::getNumberOfChars()
105{
106    document().updateLayoutIgnorePendingStylesheets();
107    return SVGTextQuery(renderer()).numberOfCharacters();
108}
109
110float SVGTextContentElement::getComputedTextLength()
111{
112    document().updateLayoutIgnorePendingStylesheets();
113    return SVGTextQuery(renderer()).textLength();
114}
115
116float SVGTextContentElement::getSubStringLength(unsigned charnum, unsigned nchars, ExceptionCode& ec)
117{
118    document().updateLayoutIgnorePendingStylesheets();
119
120    unsigned numberOfChars = getNumberOfChars();
121    if (charnum >= numberOfChars) {
122        ec = INDEX_SIZE_ERR;
123        return 0.0f;
124    }
125
126    return SVGTextQuery(renderer()).subStringLength(charnum, nchars);
127}
128
129SVGPoint SVGTextContentElement::getStartPositionOfChar(unsigned charnum, ExceptionCode& ec)
130{
131    document().updateLayoutIgnorePendingStylesheets();
132
133    if (charnum > getNumberOfChars()) {
134        ec = INDEX_SIZE_ERR;
135        return SVGPoint();
136    }
137
138    return SVGTextQuery(renderer()).startPositionOfCharacter(charnum);
139}
140
141SVGPoint SVGTextContentElement::getEndPositionOfChar(unsigned charnum, ExceptionCode& ec)
142{
143    document().updateLayoutIgnorePendingStylesheets();
144
145    if (charnum > getNumberOfChars()) {
146        ec = INDEX_SIZE_ERR;
147        return SVGPoint();
148    }
149
150    return SVGTextQuery(renderer()).endPositionOfCharacter(charnum);
151}
152
153FloatRect SVGTextContentElement::getExtentOfChar(unsigned charnum, ExceptionCode& ec)
154{
155    document().updateLayoutIgnorePendingStylesheets();
156
157    if (charnum > getNumberOfChars()) {
158        ec = INDEX_SIZE_ERR;
159        return FloatRect();
160    }
161
162    return SVGTextQuery(renderer()).extentOfCharacter(charnum);
163}
164
165float SVGTextContentElement::getRotationOfChar(unsigned charnum, ExceptionCode& ec)
166{
167    document().updateLayoutIgnorePendingStylesheets();
168
169    if (charnum > getNumberOfChars()) {
170        ec = INDEX_SIZE_ERR;
171        return 0.0f;
172    }
173
174    return SVGTextQuery(renderer()).rotationOfCharacter(charnum);
175}
176
177int SVGTextContentElement::getCharNumAtPosition(const SVGPoint& point)
178{
179    document().updateLayoutIgnorePendingStylesheets();
180    return SVGTextQuery(renderer()).characterNumberAtPosition(point);
181}
182
183void SVGTextContentElement::selectSubString(unsigned charnum, unsigned nchars, ExceptionCode& ec)
184{
185    unsigned numberOfChars = getNumberOfChars();
186    if (charnum >= numberOfChars) {
187        ec = INDEX_SIZE_ERR;
188        return;
189    }
190
191    if (nchars > numberOfChars - charnum)
192        nchars = numberOfChars - charnum;
193
194    ASSERT(document().frame());
195
196    FrameSelection& selection = document().frame()->selection();
197
198    // Find selection start
199    VisiblePosition start(firstPositionInNode(const_cast<SVGTextContentElement*>(this)));
200    for (unsigned i = 0; i < charnum; ++i)
201        start = start.next();
202
203    // Find selection end
204    VisiblePosition end(start);
205    for (unsigned i = 0; i < nchars; ++i)
206        end = end.next();
207
208    selection.setSelection(VisibleSelection(start, end));
209}
210
211bool SVGTextContentElement::isSupportedAttribute(const QualifiedName& attrName)
212{
213    static NeverDestroyed<HashSet<QualifiedName>> supportedAttributes;
214    if (supportedAttributes.get().isEmpty()) {
215        SVGLangSpace::addSupportedAttributes(supportedAttributes);
216        SVGExternalResourcesRequired::addSupportedAttributes(supportedAttributes);
217        supportedAttributes.get().add(SVGNames::lengthAdjustAttr);
218        supportedAttributes.get().add(SVGNames::textLengthAttr);
219    }
220    return supportedAttributes.get().contains<SVGAttributeHashTranslator>(attrName);
221}
222
223bool SVGTextContentElement::isPresentationAttribute(const QualifiedName& name) const
224{
225    if (name.matches(XMLNames::spaceAttr))
226        return true;
227    return SVGGraphicsElement::isPresentationAttribute(name);
228}
229
230void SVGTextContentElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
231{
232    if (!isSupportedAttribute(name))
233        SVGGraphicsElement::collectStyleForPresentationAttribute(name, value, style);
234    else if (name.matches(XMLNames::spaceAttr)) {
235        DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, preserveString, ("preserve", AtomicString::ConstructFromLiteral));
236
237        if (value == preserveString)
238            addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePre);
239        else
240            addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValueNowrap);
241    }
242}
243
244void SVGTextContentElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
245{
246    SVGParsingError parseError = NoError;
247
248    if (!isSupportedAttribute(name))
249        SVGGraphicsElement::parseAttribute(name, value);
250    else if (name == SVGNames::lengthAdjustAttr) {
251        SVGLengthAdjustType propertyValue = SVGPropertyTraits<SVGLengthAdjustType>::fromString(value);
252        if (propertyValue > 0)
253            setLengthAdjustBaseValue(propertyValue);
254    } else if (name == SVGNames::textLengthAttr) {
255        m_textLength.value = SVGLength::construct(LengthModeOther, value, parseError, ForbidNegativeLengths);
256    } else if (SVGExternalResourcesRequired::parseAttribute(name, value)) {
257    } else if (SVGLangSpace::parseAttribute(name, value)) {
258    } else
259        ASSERT_NOT_REACHED();
260
261    reportAttributeParsingError(parseError, name, value);
262}
263
264void SVGTextContentElement::svgAttributeChanged(const QualifiedName& attrName)
265{
266    if (!isSupportedAttribute(attrName)) {
267        SVGGraphicsElement::svgAttributeChanged(attrName);
268        return;
269    }
270
271    SVGElementInstance::InvalidationGuard invalidationGuard(this);
272
273    if (attrName == SVGNames::textLengthAttr)
274        m_specifiedTextLength = m_textLength.value;
275
276    if (auto renderer = this->renderer())
277        RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
278}
279
280bool SVGTextContentElement::selfHasRelativeLengths() const
281{
282    // Any element of the <text> subtree is advertized as using relative lengths.
283    // On any window size change, we have to relayout the text subtree, as the
284    // effective 'on-screen' font size may change.
285    return true;
286}
287
288SVGTextContentElement* SVGTextContentElement::elementFromRenderer(RenderObject* renderer)
289{
290    if (!renderer)
291        return 0;
292
293    if (!renderer->isSVGText() && !renderer->isSVGInline())
294        return 0;
295
296    SVGElement* element = toSVGElement(renderer->node());
297    ASSERT(element);
298
299    if (!element->isTextContent())
300        return 0;
301
302    return toSVGTextContentElement(element);
303}
304
305}
306