File indexing completed on 2024-04-28 11:39:22
0001 /* 0002 Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> 0003 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Library General Public 0007 License as published by the Free Software Foundation; either 0008 version 2 of the License, or (at your option) any later version. 0009 0010 This library is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Library General Public License for more details. 0014 0015 You should have received a copy of the GNU Library General Public License 0016 along with this library; see the file COPYING.LIB. If not, write to 0017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "wtf/Platform.h" 0022 0023 #if ENABLE(SVG) 0024 #include "SVGTextContentElement.h" 0025 0026 #include "cssvalues.h" 0027 0028 /*#include "CSSPropertyNames.h" 0029 #include "CSSValueKeywords.h"*/ 0030 #include "ExceptionCode.h" 0031 #include "FloatPoint.h" 0032 #include "FloatRect.h" 0033 /*#include "Frame.h" 0034 #include "Position.h"*/ 0035 #include "RenderSVGText.h" 0036 /*#include "SelectionController.h"*/ 0037 #include "SVGCharacterLayoutInfo.h" 0038 #include "SVGRootInlineBox.h" 0039 #include "SVGLength.h" 0040 #include "SVGInlineTextBox.h" 0041 #include "SVGNames.h" 0042 0043 namespace WebCore 0044 { 0045 0046 SVGTextContentElement::SVGTextContentElement(const QualifiedName &tagName, Document *doc) 0047 : SVGStyledElement(tagName, doc) 0048 , SVGTests() 0049 , SVGLangSpace() 0050 , SVGExternalResourcesRequired() 0051 , m_textLength(this, LengthModeOther) 0052 , m_lengthAdjust(LENGTHADJUST_SPACING) 0053 { 0054 } 0055 0056 SVGTextContentElement::~SVGTextContentElement() 0057 { 0058 } 0059 0060 ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, SVGLength, Length, length, TextLength, textLength, SVGNames::textLengthAttr, m_textLength) 0061 ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, int, Enumeration, enumeration, LengthAdjust, lengthAdjust, SVGNames::lengthAdjustAttr, m_lengthAdjust) 0062 0063 static inline float cumulativeCharacterRangeLength(const Vector<SVGChar>::iterator &start, const Vector<SVGChar>::iterator &end, SVGInlineTextBox *textBox, 0064 int startOffset, long startPosition, long length, bool isVerticalText, long &atCharacter) 0065 { 0066 if (!length) { 0067 return 0.0f; 0068 } 0069 0070 float textLength = 0.0f; 0071 RenderStyle *style = textBox->renderText()->style(); 0072 0073 bool usesFullRange = (startPosition == -1 && length == -1); 0074 0075 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 0076 if (usesFullRange || (atCharacter >= startPosition && atCharacter <= startPosition + length)) { 0077 unsigned int newOffset = textBox->start() + (it - start) + startOffset; 0078 0079 // Take RTL text into account and pick right glyph width/height. 0080 /*FIXME khtml if (textBox->direction() == RTL) 0081 newOffset = textBox->start() + textBox->end() - newOffset;*/ 0082 0083 // FIXME: does this handle multichar glyphs ok? not sure 0084 int charsConsumed = 0; 0085 String glyphName; 0086 if (isVerticalText) { 0087 textLength += textBox->calculateGlyphHeight(style, newOffset, 0); 0088 } else { 0089 textLength += textBox->calculateGlyphWidth(style, newOffset, 0, charsConsumed, glyphName); 0090 } 0091 } 0092 0093 if (!usesFullRange) { 0094 if (atCharacter == startPosition + length - 1) { 0095 break; 0096 } 0097 0098 atCharacter++; 0099 } 0100 } 0101 0102 return textLength; 0103 } 0104 0105 // Helper class for querying certain glyph information 0106 struct SVGInlineTextBoxQueryWalker { 0107 typedef enum { 0108 NumberOfCharacters, 0109 TextLength, 0110 SubStringLength, 0111 StartPosition, 0112 EndPosition, 0113 Extent, 0114 Rotation, 0115 CharacterNumberAtPosition 0116 } QueryMode; 0117 0118 SVGInlineTextBoxQueryWalker(const SVGTextContentElement *reference, QueryMode mode) 0119 : m_reference(reference) 0120 , m_mode(mode) 0121 , m_queryStartPosition(0) 0122 , m_queryLength(0) 0123 , m_queryPointInput() 0124 , m_queryLongResult(0) 0125 , m_queryFloatResult(0.0f) 0126 , m_queryPointResult() 0127 , m_queryRectResult() 0128 , m_stopProcessing(true) 0129 , m_atCharacter(0) 0130 { 0131 } 0132 0133 void chunkPortionCallback(SVGInlineTextBox *textBox, int startOffset, const AffineTransform &chunkCtm, 0134 const Vector<SVGChar>::iterator &start, const Vector<SVGChar>::iterator &end) 0135 { 0136 Q_UNUSED(chunkCtm); 0137 RenderStyle *style = textBox->renderText()->style(); 0138 bool isVerticalText = style->svgStyle()->writingMode() == WM_TBRL || style->svgStyle()->writingMode() == WM_TB; 0139 0140 switch (m_mode) { 0141 case NumberOfCharacters: { 0142 m_queryLongResult += (end - start); 0143 m_stopProcessing = false; 0144 return; 0145 } 0146 case TextLength: { 0147 float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, -1, -1, isVerticalText, m_atCharacter); 0148 0149 if (isVerticalText) { 0150 m_queryFloatResult += textLength; 0151 } else { 0152 m_queryFloatResult += textLength; 0153 } 0154 0155 m_stopProcessing = false; 0156 return; 0157 } 0158 case SubStringLength: { 0159 long startPosition = m_queryStartPosition; 0160 long length = m_queryLength; 0161 0162 float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, startPosition, length, isVerticalText, m_atCharacter); 0163 0164 if (isVerticalText) { 0165 m_queryFloatResult += textLength; 0166 } else { 0167 m_queryFloatResult += textLength; 0168 } 0169 0170 if (m_atCharacter == startPosition + length) { 0171 m_stopProcessing = true; 0172 } else { 0173 m_stopProcessing = false; 0174 } 0175 0176 return; 0177 } 0178 case StartPosition: { 0179 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 0180 if (m_atCharacter == m_queryStartPosition) { 0181 m_queryPointResult = FloatPoint(it->x, it->y); 0182 m_stopProcessing = true; 0183 return; 0184 } 0185 0186 m_atCharacter++; 0187 } 0188 0189 m_stopProcessing = false; 0190 return; 0191 } 0192 case EndPosition: { 0193 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 0194 if (m_atCharacter == m_queryStartPosition) { 0195 unsigned int newOffset = textBox->start() + (it - start) + startOffset; 0196 0197 // Take RTL text into account and pick right glyph width/height. 0198 /*FIXME khtml if (textBox->direction() == RTL) 0199 newOffset = textBox->start() + textBox->end() - newOffset;*/ 0200 0201 int charsConsumed; 0202 String glyphName; 0203 if (isVerticalText) { 0204 m_queryPointResult.move(it->x, it->y + textBox->calculateGlyphHeight(style, newOffset, end - it)); 0205 } else { 0206 m_queryPointResult.move(it->x + textBox->calculateGlyphWidth(style, newOffset, end - it, charsConsumed, glyphName), it->y); 0207 } 0208 0209 m_stopProcessing = true; 0210 return; 0211 } 0212 0213 m_atCharacter++; 0214 } 0215 0216 m_stopProcessing = false; 0217 return; 0218 } 0219 case Extent: { 0220 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 0221 if (m_atCharacter == m_queryStartPosition) { 0222 unsigned int newOffset = textBox->start() + (it - start) + startOffset; 0223 m_queryRectResult = textBox->calculateGlyphBoundaries(style, newOffset, *it); 0224 m_stopProcessing = true; 0225 return; 0226 } 0227 0228 m_atCharacter++; 0229 } 0230 0231 m_stopProcessing = false; 0232 return; 0233 } 0234 case Rotation: { 0235 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 0236 if (m_atCharacter == m_queryStartPosition) { 0237 m_queryFloatResult = it->angle; 0238 m_stopProcessing = true; 0239 return; 0240 } 0241 0242 m_atCharacter++; 0243 } 0244 0245 m_stopProcessing = false; 0246 return; 0247 } 0248 case CharacterNumberAtPosition: { 0249 int offset = 0; 0250 SVGChar *charAtPos = textBox->closestCharacterToPosition(m_queryPointInput.x(), m_queryPointInput.y(), offset); 0251 0252 offset += m_atCharacter; 0253 if (charAtPos && offset > m_queryLongResult) { 0254 m_queryLongResult = offset; 0255 } 0256 0257 m_atCharacter += end - start; 0258 m_stopProcessing = false; 0259 return; 0260 } 0261 default: 0262 ASSERT_NOT_REACHED(); 0263 m_stopProcessing = true; 0264 return; 0265 } 0266 } 0267 0268 void setQueryInputParameters(long startPosition, long length, FloatPoint referencePoint) 0269 { 0270 m_queryStartPosition = startPosition; 0271 m_queryLength = length; 0272 m_queryPointInput = referencePoint; 0273 } 0274 0275 long longResult() const 0276 { 0277 return m_queryLongResult; 0278 } 0279 float floatResult() const 0280 { 0281 return m_queryFloatResult; 0282 } 0283 FloatPoint pointResult() const 0284 { 0285 return m_queryPointResult; 0286 } 0287 FloatRect rectResult() const 0288 { 0289 return m_queryRectResult; 0290 } 0291 bool stopProcessing() const 0292 { 0293 return m_stopProcessing; 0294 } 0295 0296 private: 0297 const SVGTextContentElement *m_reference; 0298 QueryMode m_mode; 0299 0300 long m_queryStartPosition; 0301 long m_queryLength; 0302 FloatPoint m_queryPointInput; 0303 0304 long m_queryLongResult; 0305 float m_queryFloatResult; 0306 FloatPoint m_queryPointResult; 0307 FloatRect m_queryRectResult; 0308 0309 bool m_stopProcessing; 0310 long m_atCharacter; 0311 }; 0312 0313 static Vector<SVGInlineTextBox *> findInlineTextBoxInTextChunks(const SVGTextContentElement *element, const Vector<SVGTextChunk> &chunks) 0314 { 0315 Vector<SVGTextChunk>::const_iterator it = chunks.begin(); 0316 const Vector<SVGTextChunk>::const_iterator end = chunks.end(); 0317 0318 Vector<SVGInlineTextBox *> boxes; 0319 0320 for (; it != end; ++it) { 0321 Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = it->boxes.begin(); 0322 const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = it->boxes.end(); 0323 0324 for (; boxIt != boxEnd; ++boxIt) { 0325 SVGInlineTextBox *textBox = static_cast<SVGInlineTextBox *>(boxIt->box); 0326 0327 Node *textElement = textBox->renderText()->parent()->element(); 0328 ASSERT(textElement); 0329 0330 if (textElement == element || textElement->parent() == element) { 0331 boxes.append(textBox); 0332 } 0333 } 0334 } 0335 0336 return boxes; 0337 } 0338 0339 static inline SVGRootInlineBox *rootInlineBoxForTextContentElement(const SVGTextContentElement *element) 0340 { 0341 RenderObject *object = element->renderer(); 0342 0343 if (!object || !object->isSVGText() || object->isText()) { 0344 return nullptr; 0345 } 0346 0347 RenderSVGText *svgText = static_cast<RenderSVGText *>(object); 0348 0349 // Find root inline box 0350 SVGRootInlineBox *rootBox = static_cast<SVGRootInlineBox *>(svgText->firstRootBox()); 0351 if (!rootBox) { 0352 // Layout is not sync yet! 0353 /*FIXME khtml element->document()->updateLayoutIgnorePendingStylesheets();*/ 0354 rootBox = static_cast<SVGRootInlineBox *>(svgText->firstRootBox()); 0355 } 0356 0357 ASSERT(rootBox); 0358 return rootBox; 0359 } 0360 0361 static inline SVGInlineTextBoxQueryWalker executeTextQuery(const SVGTextContentElement *element, SVGInlineTextBoxQueryWalker::QueryMode mode, 0362 long startPosition = 0, long length = 0, FloatPoint referencePoint = FloatPoint()) 0363 { 0364 SVGRootInlineBox *rootBox = rootInlineBoxForTextContentElement(element); 0365 if (!rootBox) { 0366 return SVGInlineTextBoxQueryWalker(nullptr, mode); 0367 } 0368 0369 // Find all inline text box associated with our renderer 0370 Vector<SVGInlineTextBox *> textBoxes = findInlineTextBoxInTextChunks(element, rootBox->svgTextChunks()); 0371 0372 // Walk text chunks to find chunks associated with our inline text box 0373 SVGInlineTextBoxQueryWalker walkerCallback(element, mode); 0374 walkerCallback.setQueryInputParameters(startPosition, length, referencePoint); 0375 0376 SVGTextChunkWalker<SVGInlineTextBoxQueryWalker> walker(&walkerCallback, &SVGInlineTextBoxQueryWalker::chunkPortionCallback); 0377 0378 Vector<SVGInlineTextBox *>::iterator it = textBoxes.begin(); 0379 Vector<SVGInlineTextBox *>::iterator end = textBoxes.end(); 0380 0381 for (; it != end; ++it) { 0382 rootBox->walkTextChunks(&walker, *it); 0383 0384 if (walkerCallback.stopProcessing()) { 0385 break; 0386 } 0387 } 0388 0389 return walkerCallback; 0390 } 0391 0392 long SVGTextContentElement::getNumberOfChars() const 0393 { 0394 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::NumberOfCharacters).longResult(); 0395 } 0396 0397 float SVGTextContentElement::getComputedTextLength() const 0398 { 0399 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::TextLength).floatResult(); 0400 } 0401 0402 float SVGTextContentElement::getSubStringLength(long charnum, long nchars, ExceptionCode &ec) const 0403 { 0404 // Differences to SVG 1.1 spec, as the spec is clearly wrong. TODO: Raise SVG WG issue! 0405 // #1: We accept a 'long nchars' parameter instead of 'unsigned long nchars' to be able 0406 // to catch cases where someone called us with a negative 'nchars' value - in those 0407 // cases we'll just throw a 'INDEX_SIZE_ERR' (acid3 implicitly agrees with us) 0408 // 0409 // #2: We only throw if 'charnum + nchars' is greater than the number of characters, not 0410 // if it's equal, as this really doesn't make any sense (no way to measure the last character!) 0411 // 0412 // #3: If 'charnum' is greater than or equal to 'numberOfChars', we're throwing an exception here 0413 // as the result is undefined for every other value of 'nchars' than '0'. 0414 0415 long numberOfChars = getNumberOfChars(); 0416 if (charnum < 0 || nchars < 0 || numberOfChars <= charnum || charnum + nchars > numberOfChars) { 0417 ec = DOMException::INDEX_SIZE_ERR; 0418 return 0.0f; 0419 } 0420 0421 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::SubStringLength, charnum, nchars).floatResult(); 0422 } 0423 0424 FloatPoint SVGTextContentElement::getStartPositionOfChar(long charnum, ExceptionCode &ec) const 0425 { 0426 if (charnum < 0 || charnum > getNumberOfChars()) { 0427 ec = DOMException::INDEX_SIZE_ERR; 0428 return FloatPoint(); 0429 } 0430 0431 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::StartPosition, charnum).pointResult(); 0432 } 0433 0434 FloatPoint SVGTextContentElement::getEndPositionOfChar(long charnum, ExceptionCode &ec) const 0435 { 0436 if (charnum < 0 || charnum > getNumberOfChars()) { 0437 ec = DOMException::INDEX_SIZE_ERR; 0438 return FloatPoint(); 0439 } 0440 0441 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::EndPosition, charnum).pointResult(); 0442 } 0443 0444 FloatRect SVGTextContentElement::getExtentOfChar(long charnum, ExceptionCode &ec) const 0445 { 0446 if (charnum < 0 || charnum > getNumberOfChars()) { 0447 ec = DOMException::INDEX_SIZE_ERR; 0448 return FloatRect(); 0449 } 0450 0451 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Extent, charnum).rectResult(); 0452 } 0453 0454 float SVGTextContentElement::getRotationOfChar(long charnum, ExceptionCode &ec) const 0455 { 0456 if (charnum < 0 || charnum > getNumberOfChars()) { 0457 ec = DOMException::INDEX_SIZE_ERR; 0458 return 0.0f; 0459 } 0460 0461 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Rotation, charnum).floatResult(); 0462 } 0463 0464 long SVGTextContentElement::getCharNumAtPosition(const FloatPoint &point) const 0465 { 0466 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::CharacterNumberAtPosition, 0.0f, 0.0f, point).longResult(); 0467 } 0468 0469 void SVGTextContentElement::selectSubString(long charnum, long nchars, ExceptionCode &ec) const 0470 { 0471 long numberOfChars = getNumberOfChars(); 0472 if (charnum < 0 || nchars < 0 || charnum > numberOfChars) { 0473 ec = DOMException::INDEX_SIZE_ERR; 0474 return; 0475 } 0476 0477 if (nchars > numberOfChars - charnum) { 0478 nchars = numberOfChars - charnum; 0479 } 0480 0481 ASSERT(document()); 0482 //khtml ASSERT(document()->frame()); 0483 0484 /*FIXME SelectionController* controller = document()->frame()->selectionController(); 0485 if (!controller) 0486 return; 0487 0488 // Find selection start 0489 VisiblePosition start(const_cast<SVGTextContentElement*>(this), 0, SEL_DEFAULT_AFFINITY); 0490 for (long i = 0; i < charnum; ++i) 0491 start = start.next(); 0492 0493 // Find selection end 0494 VisiblePosition end(start); 0495 for (long i = 0; i < nchars; ++i) 0496 end = end.next(); 0497 0498 controller->setSelection(Selection(start, end));*/ 0499 } 0500 0501 void SVGTextContentElement::parseMappedAttribute(MappedAttribute *attr) 0502 { 0503 if (attr->name() == SVGNames::lengthAdjustAttr) { 0504 if (attr->value() == "spacing") { 0505 setLengthAdjustBaseValue(LENGTHADJUST_SPACING); 0506 } else if (attr->value() == "spacingAndGlyphs") { 0507 setLengthAdjustBaseValue(LENGTHADJUST_SPACINGANDGLYPHS); 0508 } 0509 } else if (attr->name() == SVGNames::textLengthAttr) { 0510 setTextLengthBaseValue(SVGLength(this, LengthModeOther, attr->value())); 0511 if (textLength().value() < 0.0) { 0512 document()->accessSVGExtensions()->reportError("A negative value for text attribute <textLength> is not allowed"); 0513 } 0514 } else { 0515 if (SVGTests::parseMappedAttribute(attr)) { 0516 return; 0517 } 0518 if (SVGLangSpace::parseMappedAttribute(attr)) { 0519 if (attr->id() == ATTR_XML_SPACE) { 0520 static const DOMString preserveString("preserve"); 0521 0522 if (attr->value() == preserveString) { 0523 addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_PRE); 0524 } else { 0525 addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_NOWRAP); 0526 } 0527 } 0528 return; 0529 } 0530 if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) { 0531 return; 0532 } 0533 0534 SVGStyledElement::parseMappedAttribute(attr); 0535 } 0536 } 0537 0538 bool SVGTextContentElement::isKnownAttribute(const QualifiedName &attrName) 0539 { 0540 return (attrName.matches(SVGNames::lengthAdjustAttr) || 0541 attrName.matches(SVGNames::textLengthAttr) || 0542 SVGTests::isKnownAttribute(attrName) || 0543 SVGLangSpace::isKnownAttribute(attrName) || 0544 SVGExternalResourcesRequired::isKnownAttribute(attrName) || 0545 SVGStyledElement::isKnownAttribute(attrName)); 0546 } 0547 0548 } 0549 0550 #endif // ENABLE(SVG)