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