1 /* 2 * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann (at) kde.org> 3 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Rob Buis <buis (at) 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 "bindings/core/v8/ExceptionMessages.h" 25 #include "bindings/core/v8/ExceptionState.h" 26 #include "bindings/core/v8/ExceptionStatePlaceholder.h" 27 #include "core/CSSPropertyNames.h" 28 #include "core/CSSValueKeywords.h" 29 #include "core/SVGNames.h" 30 #include "core/XMLNames.h" 31 #include "core/editing/FrameSelection.h" 32 #include "core/frame/LocalFrame.h" 33 #include "core/rendering/RenderObject.h" 34 #include "core/rendering/svg/RenderSVGResource.h" 35 #include "core/rendering/svg/SVGTextQuery.h" 36 37 namespace blink { 38 39 template<> const SVGEnumerationStringEntries& getStaticStringEntries<SVGLengthAdjustType>() 40 { 41 DEFINE_STATIC_LOCAL(SVGEnumerationStringEntries, entries, ()); 42 if (entries.isEmpty()) { 43 entries.append(std::make_pair(SVGLengthAdjustSpacing, "spacing")); 44 entries.append(std::make_pair(SVGLengthAdjustSpacingAndGlyphs, "spacingAndGlyphs")); 45 } 46 return entries; 47 } 48 49 // SVGTextContentElement's 'textLength' attribute needs special handling. 50 // It should return getComputedTextLength() when textLength is not specified manually. 51 class SVGAnimatedTextLength FINAL : public SVGAnimatedLength { 52 public: 53 static PassRefPtr<SVGAnimatedTextLength> create(SVGTextContentElement* contextElement) 54 { 55 return adoptRef(new SVGAnimatedTextLength(contextElement)); 56 } 57 58 virtual SVGLengthTearOff* baseVal() OVERRIDE 59 { 60 SVGTextContentElement* textContentElement = toSVGTextContentElement(contextElement()); 61 if (!textContentElement->textLengthIsSpecifiedByUser()) 62 baseValue()->newValueSpecifiedUnits(LengthTypeNumber, textContentElement->getComputedTextLength()); 63 64 return SVGAnimatedLength::baseVal(); 65 } 66 67 private: 68 SVGAnimatedTextLength(SVGTextContentElement* contextElement) 69 : SVGAnimatedLength(contextElement, SVGNames::textLengthAttr, SVGLength::create(LengthModeOther), ForbidNegativeLengths) 70 { 71 } 72 }; 73 74 75 SVGTextContentElement::SVGTextContentElement(const QualifiedName& tagName, Document& document) 76 : SVGGraphicsElement(tagName, document) 77 , m_textLength(SVGAnimatedTextLength::create(this)) 78 , m_textLengthIsSpecifiedByUser(false) 79 , m_lengthAdjust(SVGAnimatedEnumeration<SVGLengthAdjustType>::create(this, SVGNames::lengthAdjustAttr, SVGLengthAdjustSpacing)) 80 { 81 addToPropertyMap(m_textLength); 82 addToPropertyMap(m_lengthAdjust); 83 } 84 85 unsigned SVGTextContentElement::getNumberOfChars() 86 { 87 document().updateLayoutIgnorePendingStylesheets(); 88 return SVGTextQuery(renderer()).numberOfCharacters(); 89 } 90 91 float SVGTextContentElement::getComputedTextLength() 92 { 93 document().updateLayoutIgnorePendingStylesheets(); 94 return SVGTextQuery(renderer()).textLength(); 95 } 96 97 float SVGTextContentElement::getSubStringLength(unsigned charnum, unsigned nchars, ExceptionState& exceptionState) 98 { 99 document().updateLayoutIgnorePendingStylesheets(); 100 101 unsigned numberOfChars = getNumberOfChars(); 102 if (charnum >= numberOfChars) { 103 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexExceedsMaximumBound("charnum", charnum, getNumberOfChars())); 104 return 0.0f; 105 } 106 107 if (nchars > numberOfChars - charnum) 108 nchars = numberOfChars - charnum; 109 110 return SVGTextQuery(renderer()).subStringLength(charnum, nchars); 111 } 112 113 PassRefPtr<SVGPointTearOff> SVGTextContentElement::getStartPositionOfChar(unsigned charnum, ExceptionState& exceptionState) 114 { 115 document().updateLayoutIgnorePendingStylesheets(); 116 117 if (charnum > getNumberOfChars()) { 118 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexExceedsMaximumBound("charnum", charnum, getNumberOfChars())); 119 return nullptr; 120 } 121 122 FloatPoint point = SVGTextQuery(renderer()).startPositionOfCharacter(charnum); 123 return SVGPointTearOff::create(SVGPoint::create(point), 0, PropertyIsNotAnimVal); 124 } 125 126 PassRefPtr<SVGPointTearOff> SVGTextContentElement::getEndPositionOfChar(unsigned charnum, ExceptionState& exceptionState) 127 { 128 document().updateLayoutIgnorePendingStylesheets(); 129 130 if (charnum > getNumberOfChars()) { 131 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexExceedsMaximumBound("charnum", charnum, getNumberOfChars())); 132 return nullptr; 133 } 134 135 FloatPoint point = SVGTextQuery(renderer()).endPositionOfCharacter(charnum); 136 return SVGPointTearOff::create(SVGPoint::create(point), 0, PropertyIsNotAnimVal); 137 } 138 139 PassRefPtr<SVGRectTearOff> SVGTextContentElement::getExtentOfChar(unsigned charnum, ExceptionState& exceptionState) 140 { 141 document().updateLayoutIgnorePendingStylesheets(); 142 143 if (charnum > getNumberOfChars()) { 144 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexExceedsMaximumBound("charnum", charnum, getNumberOfChars())); 145 return nullptr; 146 } 147 148 FloatRect rect = SVGTextQuery(renderer()).extentOfCharacter(charnum); 149 return SVGRectTearOff::create(SVGRect::create(rect), 0, PropertyIsNotAnimVal); 150 } 151 152 float SVGTextContentElement::getRotationOfChar(unsigned charnum, ExceptionState& exceptionState) 153 { 154 document().updateLayoutIgnorePendingStylesheets(); 155 156 if (charnum > getNumberOfChars()) { 157 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexExceedsMaximumBound("charnum", charnum, getNumberOfChars())); 158 return 0.0f; 159 } 160 161 return SVGTextQuery(renderer()).rotationOfCharacter(charnum); 162 } 163 164 int SVGTextContentElement::getCharNumAtPosition(PassRefPtr<SVGPointTearOff> point, ExceptionState& exceptionState) 165 { 166 document().updateLayoutIgnorePendingStylesheets(); 167 return SVGTextQuery(renderer()).characterNumberAtPosition(point->target()->value()); 168 } 169 170 void SVGTextContentElement::selectSubString(unsigned charnum, unsigned nchars, ExceptionState& exceptionState) 171 { 172 unsigned numberOfChars = getNumberOfChars(); 173 if (charnum >= numberOfChars) { 174 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexExceedsMaximumBound("charnum", charnum, getNumberOfChars())); 175 return; 176 } 177 178 if (nchars > numberOfChars - charnum) 179 nchars = numberOfChars - charnum; 180 181 ASSERT(document().frame()); 182 183 // Find selection start 184 VisiblePosition start(firstPositionInNode(const_cast<SVGTextContentElement*>(this))); 185 for (unsigned i = 0; i < charnum; ++i) 186 start = start.next(); 187 188 // Find selection end 189 VisiblePosition end(start); 190 for (unsigned i = 0; i < nchars; ++i) 191 end = end.next(); 192 193 document().frame()->selection().setSelection(VisibleSelection(start, end)); 194 } 195 196 bool SVGTextContentElement::isSupportedAttribute(const QualifiedName& attrName) 197 { 198 DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, supportedAttributes, ()); 199 if (supportedAttributes.isEmpty()) { 200 supportedAttributes.add(SVGNames::lengthAdjustAttr); 201 supportedAttributes.add(SVGNames::textLengthAttr); 202 supportedAttributes.add(XMLNames::spaceAttr); 203 } 204 return supportedAttributes.contains<SVGAttributeHashTranslator>(attrName); 205 } 206 207 bool SVGTextContentElement::isPresentationAttribute(const QualifiedName& name) const 208 { 209 if (name.matches(XMLNames::spaceAttr)) 210 return true; 211 return SVGGraphicsElement::isPresentationAttribute(name); 212 } 213 214 void SVGTextContentElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style) 215 { 216 if (!isSupportedAttribute(name)) 217 SVGGraphicsElement::collectStyleForPresentationAttribute(name, value, style); 218 else if (name.matches(XMLNames::spaceAttr)) { 219 DEFINE_STATIC_LOCAL(const AtomicString, preserveString, ("preserve", AtomicString::ConstructFromLiteral)); 220 221 if (value == preserveString) 222 addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePre); 223 else 224 addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValueNowrap); 225 } 226 } 227 228 void SVGTextContentElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 229 { 230 SVGParsingError parseError = NoError; 231 232 if (!isSupportedAttribute(name)) 233 SVGGraphicsElement::parseAttribute(name, value); 234 else if (name == SVGNames::lengthAdjustAttr) { 235 m_lengthAdjust->setBaseValueAsString(value, parseError); 236 } else if (name == SVGNames::textLengthAttr) { 237 m_textLength->setBaseValueAsString(value, parseError); 238 } else if (name.matches(XMLNames::spaceAttr)) { 239 } else 240 ASSERT_NOT_REACHED(); 241 242 reportAttributeParsingError(parseError, name, value); 243 } 244 245 void SVGTextContentElement::svgAttributeChanged(const QualifiedName& attrName) 246 { 247 if (!isSupportedAttribute(attrName)) { 248 SVGGraphicsElement::svgAttributeChanged(attrName); 249 return; 250 } 251 252 if (attrName == SVGNames::textLengthAttr) 253 m_textLengthIsSpecifiedByUser = true; 254 255 SVGElement::InvalidationGuard invalidationGuard(this); 256 257 if (RenderObject* renderer = this->renderer()) 258 RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer); 259 } 260 261 bool SVGTextContentElement::selfHasRelativeLengths() const 262 { 263 // Any element of the <text> subtree is advertized as using relative lengths. 264 // On any window size change, we have to relayout the text subtree, as the 265 // effective 'on-screen' font size may change. 266 return true; 267 } 268 269 SVGTextContentElement* SVGTextContentElement::elementFromRenderer(RenderObject* renderer) 270 { 271 if (!renderer) 272 return 0; 273 274 if (!renderer->isSVGText() && !renderer->isSVGInline()) 275 return 0; 276 277 SVGElement* element = toSVGElement(renderer->node()); 278 ASSERT(element); 279 return isSVGTextContentElement(*element) ? toSVGTextContentElement(element) : 0; 280 } 281 282 } 283