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 "core/svg/SVGTextContentElement.h"
23
24#include "CSSPropertyNames.h"
25#include "CSSValueKeywords.h"
26#include "SVGNames.h"
27#include "XMLNames.h"
28#include "bindings/v8/ExceptionState.h"
29#include "bindings/v8/ExceptionStatePlaceholder.h"
30#include "core/editing/FrameSelection.h"
31#include "core/frame/Frame.h"
32#include "core/rendering/RenderObject.h"
33#include "core/rendering/svg/RenderSVGResource.h"
34#include "core/rendering/svg/SVGTextQuery.h"
35#include "core/svg/SVGElementInstance.h"
36
37namespace WebCore {
38
39// Define custom animated property 'textLength'.
40const SVGPropertyInfo* SVGTextContentElement::textLengthPropertyInfo()
41{
42    static const SVGPropertyInfo* s_propertyInfo = 0;
43    if (!s_propertyInfo) {
44        s_propertyInfo = new SVGPropertyInfo(AnimatedLength,
45                                             PropertyIsReadWrite,
46                                             SVGNames::textLengthAttr,
47                                             SVGNames::textLengthAttr.localName(),
48                                             &SVGTextContentElement::synchronizeTextLength,
49                                             &SVGTextContentElement::lookupOrCreateTextLengthWrapper);
50    }
51    return s_propertyInfo;
52}
53
54// Animated property definitions
55DEFINE_ANIMATED_ENUMERATION(SVGTextContentElement, SVGNames::lengthAdjustAttr, LengthAdjust, lengthAdjust, SVGLengthAdjustType)
56DEFINE_ANIMATED_BOOLEAN(SVGTextContentElement, SVGNames::externalResourcesRequiredAttr, ExternalResourcesRequired, externalResourcesRequired)
57
58BEGIN_REGISTER_ANIMATED_PROPERTIES(SVGTextContentElement)
59    REGISTER_LOCAL_ANIMATED_PROPERTY(textLength)
60    REGISTER_LOCAL_ANIMATED_PROPERTY(lengthAdjust)
61    REGISTER_LOCAL_ANIMATED_PROPERTY(externalResourcesRequired)
62    REGISTER_PARENT_ANIMATED_PROPERTIES(SVGGraphicsElement)
63END_REGISTER_ANIMATED_PROPERTIES
64
65SVGTextContentElement::SVGTextContentElement(const QualifiedName& tagName, Document& document)
66    : SVGGraphicsElement(tagName, document)
67    , m_textLength(LengthModeOther)
68    , m_specifiedTextLength(LengthModeOther)
69    , m_lengthAdjust(SVGLengthAdjustSpacing)
70{
71    ScriptWrappable::init(this);
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::textLength()
94{
95    DEFINE_STATIC_LOCAL(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, ExceptionState& exceptionState)
117{
118    document().updateLayoutIgnorePendingStylesheets();
119
120    unsigned numberOfChars = getNumberOfChars();
121    if (charnum >= numberOfChars) {
122        exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError);
123        return 0.0f;
124    }
125
126    if (nchars > numberOfChars - charnum)
127        nchars = numberOfChars - charnum;
128
129    return SVGTextQuery(renderer()).subStringLength(charnum, nchars);
130}
131
132SVGPoint SVGTextContentElement::getStartPositionOfChar(unsigned charnum, ExceptionState& exceptionState)
133{
134    document().updateLayoutIgnorePendingStylesheets();
135
136    if (charnum > getNumberOfChars()) {
137        exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError);
138        return FloatPoint();
139    }
140
141    return SVGTextQuery(renderer()).startPositionOfCharacter(charnum);
142}
143
144SVGPoint SVGTextContentElement::getEndPositionOfChar(unsigned charnum, ExceptionState& exceptionState)
145{
146    document().updateLayoutIgnorePendingStylesheets();
147
148    if (charnum > getNumberOfChars()) {
149        exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError);
150        return FloatPoint();
151    }
152
153    return SVGTextQuery(renderer()).endPositionOfCharacter(charnum);
154}
155
156SVGRect SVGTextContentElement::getExtentOfChar(unsigned charnum, ExceptionState& exceptionState)
157{
158    document().updateLayoutIgnorePendingStylesheets();
159
160    if (charnum > getNumberOfChars()) {
161        exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError);
162        return SVGRect();
163    }
164
165    return SVGTextQuery(renderer()).extentOfCharacter(charnum);
166}
167
168float SVGTextContentElement::getRotationOfChar(unsigned charnum, ExceptionState& exceptionState)
169{
170    document().updateLayoutIgnorePendingStylesheets();
171
172    if (charnum > getNumberOfChars()) {
173        exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError);
174        return 0.0f;
175    }
176
177    return SVGTextQuery(renderer()).rotationOfCharacter(charnum);
178}
179
180int SVGTextContentElement::getCharNumAtPosition(const SVGPoint& point)
181{
182    document().updateLayoutIgnorePendingStylesheets();
183    return SVGTextQuery(renderer()).characterNumberAtPosition(point);
184}
185
186void SVGTextContentElement::selectSubString(unsigned charnum, unsigned nchars, ExceptionState& exceptionState)
187{
188    unsigned numberOfChars = getNumberOfChars();
189    if (charnum >= numberOfChars) {
190        exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError);
191        return;
192    }
193
194    if (nchars > numberOfChars - charnum)
195        nchars = numberOfChars - charnum;
196
197    ASSERT(document().frame());
198
199    // Find selection start
200    VisiblePosition start(firstPositionInNode(const_cast<SVGTextContentElement*>(this)));
201    for (unsigned i = 0; i < charnum; ++i)
202        start = start.next();
203
204    // Find selection end
205    VisiblePosition end(start);
206    for (unsigned i = 0; i < nchars; ++i)
207        end = end.next();
208
209    document().frame()->selection().setSelection(VisibleSelection(start, end));
210}
211
212bool SVGTextContentElement::isSupportedAttribute(const QualifiedName& attrName)
213{
214    DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, supportedAttributes, ());
215    if (supportedAttributes.isEmpty()) {
216        SVGExternalResourcesRequired::addSupportedAttributes(supportedAttributes);
217        supportedAttributes.add(SVGNames::lengthAdjustAttr);
218        supportedAttributes.add(SVGNames::textLengthAttr);
219        supportedAttributes.add(XMLNames::spaceAttr);
220    }
221    return supportedAttributes.contains<SVGAttributeHashTranslator>(attrName);
222}
223
224bool SVGTextContentElement::isPresentationAttribute(const QualifiedName& name) const
225{
226    if (name.matches(XMLNames::spaceAttr))
227        return true;
228    return SVGGraphicsElement::isPresentationAttribute(name);
229}
230
231void SVGTextContentElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
232{
233    if (!isSupportedAttribute(name))
234        SVGGraphicsElement::collectStyleForPresentationAttribute(name, value, style);
235    else if (name.matches(XMLNames::spaceAttr)) {
236        DEFINE_STATIC_LOCAL(const AtomicString, preserveString, ("preserve", AtomicString::ConstructFromLiteral));
237
238        if (value == preserveString)
239            addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePre);
240        else
241            addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValueNowrap);
242    }
243}
244
245void SVGTextContentElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
246{
247    SVGParsingError parseError = NoError;
248
249    if (!isSupportedAttribute(name))
250        SVGGraphicsElement::parseAttribute(name, value);
251    else if (name == SVGNames::lengthAdjustAttr) {
252        SVGLengthAdjustType propertyValue = SVGPropertyTraits<SVGLengthAdjustType>::fromString(value);
253        if (propertyValue > 0)
254            setLengthAdjustBaseValue(propertyValue);
255    } else if (name == SVGNames::textLengthAttr) {
256        m_textLength.value = SVGLength::construct(LengthModeOther, value, parseError, ForbidNegativeLengths);
257    } else if (SVGExternalResourcesRequired::parseAttribute(name, value)) {
258    } else if (name.matches(XMLNames::spaceAttr)) {
259    } else
260        ASSERT_NOT_REACHED();
261
262    reportAttributeParsingError(parseError, name, value);
263}
264
265void SVGTextContentElement::svgAttributeChanged(const QualifiedName& attrName)
266{
267    if (!isSupportedAttribute(attrName)) {
268        SVGGraphicsElement::svgAttributeChanged(attrName);
269        return;
270    }
271
272    SVGElementInstance::InvalidationGuard invalidationGuard(this);
273
274    if (attrName == SVGNames::textLengthAttr)
275        m_specifiedTextLength = m_textLength.value;
276
277    if (RenderObject* renderer = this->renderer())
278        RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
279}
280
281bool SVGTextContentElement::selfHasRelativeLengths() const
282{
283    // Any element of the <text> subtree is advertized as using relative lengths.
284    // On any window size change, we have to relayout the text subtree, as the
285    // effective 'on-screen' font size may change.
286    return true;
287}
288
289SVGTextContentElement* SVGTextContentElement::elementFromRenderer(RenderObject* renderer)
290{
291    if (!renderer)
292        return 0;
293
294    if (!renderer->isSVGText() && !renderer->isSVGInline())
295        return 0;
296
297    SVGElement* element = toSVGElement(renderer->node());
298    ASSERT(element);
299
300    if (!element->isTextContent())
301        return 0;
302
303    return toSVGTextContentElement(element);
304}
305
306}
307