KHTML
SVGTextContentElement.cpp
Go to the documentation of this file.
00001 /* 00002 Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> 00003 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License as published by the Free Software Foundation; either 00008 version 2 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "config.h" 00022 #include "wtf/Platform.h" 00023 00024 #if ENABLE(SVG) 00025 #include "SVGTextContentElement.h" 00026 00027 #include "cssvalues.h" 00028 00029 /*#include "CSSPropertyNames.h" 00030 #include "CSSValueKeywords.h"*/ 00031 #include "ExceptionCode.h" 00032 #include "FloatPoint.h" 00033 #include "FloatRect.h" 00034 /*#include "Frame.h" 00035 #include "Position.h"*/ 00036 #include "RenderSVGText.h" 00037 /*#include "SelectionController.h"*/ 00038 #include "SVGCharacterLayoutInfo.h" 00039 #include "SVGRootInlineBox.h" 00040 #include "SVGLength.h" 00041 #include "SVGInlineTextBox.h" 00042 #include "SVGNames.h" 00043 00044 namespace WebCore { 00045 00046 SVGTextContentElement::SVGTextContentElement(const QualifiedName& tagName, Document* doc) 00047 : SVGStyledElement(tagName, doc) 00048 , SVGTests() 00049 , SVGLangSpace() 00050 , SVGExternalResourcesRequired() 00051 , m_textLength(this, LengthModeOther) 00052 , m_lengthAdjust(LENGTHADJUST_SPACING) 00053 { 00054 } 00055 00056 SVGTextContentElement::~SVGTextContentElement() 00057 { 00058 } 00059 00060 ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, SVGLength, Length, length, TextLength, textLength, SVGNames::textLengthAttr, m_textLength) 00061 ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, int, Enumeration, enumeration, LengthAdjust, lengthAdjust, SVGNames::lengthAdjustAttr, m_lengthAdjust) 00062 00063 static inline float cumulativeCharacterRangeLength(const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end, SVGInlineTextBox* textBox, 00064 int startOffset, long startPosition, long length, bool isVerticalText, long& atCharacter) 00065 { 00066 if (!length) 00067 return 0.0f; 00068 00069 float textLength = 0.0f; 00070 RenderStyle* style = textBox->renderText()->style(); 00071 00072 bool usesFullRange = (startPosition == -1 && length == -1); 00073 00074 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 00075 if (usesFullRange || (atCharacter >= startPosition && atCharacter <= startPosition + length)) { 00076 unsigned int newOffset = textBox->start() + (it - start) + startOffset; 00077 00078 // Take RTL text into account and pick right glyph width/height. 00079 /*FIXME khtml if (textBox->direction() == RTL) 00080 newOffset = textBox->start() + textBox->end() - newOffset;*/ 00081 00082 // FIXME: does this handle multichar glyphs ok? not sure 00083 int charsConsumed = 0; 00084 String glyphName; 00085 if (isVerticalText) 00086 textLength += textBox->calculateGlyphHeight(style, newOffset, 0); 00087 else 00088 textLength += textBox->calculateGlyphWidth(style, newOffset, 0, charsConsumed, glyphName); 00089 } 00090 00091 if (!usesFullRange) { 00092 if (atCharacter == startPosition + length - 1) 00093 break; 00094 00095 atCharacter++; 00096 } 00097 } 00098 00099 return textLength; 00100 } 00101 00102 // Helper class for querying certain glyph information 00103 struct SVGInlineTextBoxQueryWalker { 00104 typedef enum { 00105 NumberOfCharacters, 00106 TextLength, 00107 SubStringLength, 00108 StartPosition, 00109 EndPosition, 00110 Extent, 00111 Rotation, 00112 CharacterNumberAtPosition 00113 } QueryMode; 00114 00115 SVGInlineTextBoxQueryWalker(const SVGTextContentElement* reference, QueryMode mode) 00116 : m_reference(reference) 00117 , m_mode(mode) 00118 , m_queryStartPosition(0) 00119 , m_queryLength(0) 00120 , m_queryPointInput() 00121 , m_queryLongResult(0) 00122 , m_queryFloatResult(0.0f) 00123 , m_queryPointResult() 00124 , m_queryRectResult() 00125 , m_stopProcessing(true) 00126 , m_atCharacter(0) 00127 { 00128 } 00129 00130 void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const AffineTransform& chunkCtm, 00131 const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end) 00132 { 00133 Q_UNUSED(chunkCtm); 00134 RenderStyle* style = textBox->renderText()->style(); 00135 bool isVerticalText = style->svgStyle()->writingMode() == WM_TBRL || style->svgStyle()->writingMode() == WM_TB; 00136 00137 switch (m_mode) { 00138 case NumberOfCharacters: 00139 { 00140 m_queryLongResult += (end - start); 00141 m_stopProcessing = false; 00142 return; 00143 } 00144 case TextLength: 00145 { 00146 float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, -1, -1, isVerticalText, m_atCharacter); 00147 00148 if (isVerticalText) 00149 m_queryFloatResult += textLength; 00150 else 00151 m_queryFloatResult += textLength; 00152 00153 m_stopProcessing = false; 00154 return; 00155 } 00156 case SubStringLength: 00157 { 00158 long startPosition = m_queryStartPosition; 00159 long length = m_queryLength; 00160 00161 float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, startPosition, length, isVerticalText, m_atCharacter); 00162 00163 if (isVerticalText) 00164 m_queryFloatResult += textLength; 00165 else 00166 m_queryFloatResult += textLength; 00167 00168 if (m_atCharacter == startPosition + length) 00169 m_stopProcessing = true; 00170 else 00171 m_stopProcessing = false; 00172 00173 return; 00174 } 00175 case StartPosition: 00176 { 00177 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 00178 if (m_atCharacter == m_queryStartPosition) { 00179 m_queryPointResult = FloatPoint(it->x, it->y); 00180 m_stopProcessing = true; 00181 return; 00182 } 00183 00184 m_atCharacter++; 00185 } 00186 00187 m_stopProcessing = false; 00188 return; 00189 } 00190 case EndPosition: 00191 { 00192 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 00193 if (m_atCharacter == m_queryStartPosition) { 00194 unsigned int newOffset = textBox->start() + (it - start) + startOffset; 00195 00196 // Take RTL text into account and pick right glyph width/height. 00197 /*FIXME khtml if (textBox->direction() == RTL) 00198 newOffset = textBox->start() + textBox->end() - newOffset;*/ 00199 00200 int charsConsumed; 00201 String glyphName; 00202 if (isVerticalText) 00203 m_queryPointResult.move(it->x, it->y + textBox->calculateGlyphHeight(style, newOffset, end - it)); 00204 else 00205 m_queryPointResult.move(it->x + textBox->calculateGlyphWidth(style, newOffset, end - it, charsConsumed, glyphName), it->y); 00206 00207 m_stopProcessing = true; 00208 return; 00209 } 00210 00211 m_atCharacter++; 00212 } 00213 00214 m_stopProcessing = false; 00215 return; 00216 } 00217 case Extent: 00218 { 00219 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 00220 if (m_atCharacter == m_queryStartPosition) { 00221 unsigned int newOffset = textBox->start() + (it - start) + startOffset; 00222 m_queryRectResult = textBox->calculateGlyphBoundaries(style, newOffset, *it); 00223 m_stopProcessing = true; 00224 return; 00225 } 00226 00227 m_atCharacter++; 00228 } 00229 00230 m_stopProcessing = false; 00231 return; 00232 } 00233 case Rotation: 00234 { 00235 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 00236 if (m_atCharacter == m_queryStartPosition) { 00237 m_queryFloatResult = it->angle; 00238 m_stopProcessing = true; 00239 return; 00240 } 00241 00242 m_atCharacter++; 00243 } 00244 00245 m_stopProcessing = false; 00246 return; 00247 } 00248 case CharacterNumberAtPosition: 00249 { 00250 int offset = 0; 00251 SVGChar* charAtPos = textBox->closestCharacterToPosition(m_queryPointInput.x(), m_queryPointInput.y(), offset); 00252 00253 offset += m_atCharacter; 00254 if (charAtPos && offset > m_queryLongResult) 00255 m_queryLongResult = offset; 00256 00257 m_atCharacter += end - start; 00258 m_stopProcessing = false; 00259 return; 00260 } 00261 default: 00262 ASSERT_NOT_REACHED(); 00263 m_stopProcessing = true; 00264 return; 00265 } 00266 } 00267 00268 void setQueryInputParameters(long startPosition, long length, FloatPoint referencePoint) 00269 { 00270 m_queryStartPosition = startPosition; 00271 m_queryLength = length; 00272 m_queryPointInput = referencePoint; 00273 } 00274 00275 long longResult() const { return m_queryLongResult; } 00276 float floatResult() const { return m_queryFloatResult; } 00277 FloatPoint pointResult() const { return m_queryPointResult; } 00278 FloatRect rectResult() const { return m_queryRectResult; } 00279 bool stopProcessing() const { return m_stopProcessing; } 00280 00281 private: 00282 const SVGTextContentElement* m_reference; 00283 QueryMode m_mode; 00284 00285 long m_queryStartPosition; 00286 long m_queryLength; 00287 FloatPoint m_queryPointInput; 00288 00289 long m_queryLongResult; 00290 float m_queryFloatResult; 00291 FloatPoint m_queryPointResult; 00292 FloatRect m_queryRectResult; 00293 00294 bool m_stopProcessing; 00295 long m_atCharacter; 00296 }; 00297 00298 static Vector<SVGInlineTextBox*> findInlineTextBoxInTextChunks(const SVGTextContentElement* element, const Vector<SVGTextChunk>& chunks) 00299 { 00300 Vector<SVGTextChunk>::const_iterator it = chunks.begin(); 00301 const Vector<SVGTextChunk>::const_iterator end = chunks.end(); 00302 00303 Vector<SVGInlineTextBox*> boxes; 00304 00305 for (; it != end; ++it) { 00306 Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = it->boxes.begin(); 00307 const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = it->boxes.end(); 00308 00309 for (; boxIt != boxEnd; ++boxIt) { 00310 SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(boxIt->box); 00311 00312 Node* textElement = textBox->renderText()->parent()->element(); 00313 ASSERT(textElement); 00314 00315 if (textElement == element || textElement->parent() == element) 00316 boxes.append(textBox); 00317 } 00318 } 00319 00320 return boxes; 00321 } 00322 00323 static inline SVGRootInlineBox* rootInlineBoxForTextContentElement(const SVGTextContentElement* element) 00324 { 00325 RenderObject* object = element->renderer(); 00326 00327 if (!object || !object->isSVGText() || object->isText()) 00328 return 0; 00329 00330 RenderSVGText* svgText = static_cast<RenderSVGText*>(object); 00331 00332 // Find root inline box 00333 SVGRootInlineBox* rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox()); 00334 if (!rootBox) { 00335 // Layout is not sync yet! 00336 /*FIXME khtml element->document()->updateLayoutIgnorePendingStylesheets();*/ 00337 rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox()); 00338 } 00339 00340 ASSERT(rootBox); 00341 return rootBox; 00342 } 00343 00344 static inline SVGInlineTextBoxQueryWalker executeTextQuery(const SVGTextContentElement* element, SVGInlineTextBoxQueryWalker::QueryMode mode, 00345 long startPosition = 0, long length = 0, FloatPoint referencePoint = FloatPoint()) 00346 { 00347 SVGRootInlineBox* rootBox = rootInlineBoxForTextContentElement(element); 00348 if (!rootBox) 00349 return SVGInlineTextBoxQueryWalker(0, mode); 00350 00351 // Find all inline text box associated with our renderer 00352 Vector<SVGInlineTextBox*> textBoxes = findInlineTextBoxInTextChunks(element, rootBox->svgTextChunks()); 00353 00354 // Walk text chunks to find chunks associated with our inline text box 00355 SVGInlineTextBoxQueryWalker walkerCallback(element, mode); 00356 walkerCallback.setQueryInputParameters(startPosition, length, referencePoint); 00357 00358 SVGTextChunkWalker<SVGInlineTextBoxQueryWalker> walker(&walkerCallback, &SVGInlineTextBoxQueryWalker::chunkPortionCallback); 00359 00360 Vector<SVGInlineTextBox*>::iterator it = textBoxes.begin(); 00361 Vector<SVGInlineTextBox*>::iterator end = textBoxes.end(); 00362 00363 for (; it != end; ++it) { 00364 rootBox->walkTextChunks(&walker, *it); 00365 00366 if (walkerCallback.stopProcessing()) 00367 break; 00368 } 00369 00370 return walkerCallback; 00371 } 00372 00373 long SVGTextContentElement::getNumberOfChars() const 00374 { 00375 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::NumberOfCharacters).longResult(); 00376 } 00377 00378 float SVGTextContentElement::getComputedTextLength() const 00379 { 00380 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::TextLength).floatResult(); 00381 } 00382 00383 float SVGTextContentElement::getSubStringLength(long charnum, long nchars, ExceptionCode& ec) const 00384 { 00385 // Differences to SVG 1.1 spec, as the spec is clearly wrong. TODO: Raise SVG WG issue! 00386 // #1: We accept a 'long nchars' parameter instead of 'unsigned long nchars' to be able 00387 // to catch cases where someone called us with a negative 'nchars' value - in those 00388 // cases we'll just throw a 'INDEX_SIZE_ERR' (acid3 implicitly agrees with us) 00389 // 00390 // #2: We only throw if 'charnum + nchars' is greater than the number of characters, not 00391 // if it's equal, as this really doesn't make any sense (no way to measure the last character!) 00392 // 00393 // #3: If 'charnum' is greater than or equal to 'numberOfChars', we're throwing an exception here 00394 // as the result is undefined for every other value of 'nchars' than '0'. 00395 00396 long numberOfChars = getNumberOfChars(); 00397 if (charnum < 0 || nchars < 0 || numberOfChars <= charnum || charnum + nchars > numberOfChars) { 00398 ec = DOMException::INDEX_SIZE_ERR; 00399 return 0.0f; 00400 } 00401 00402 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::SubStringLength, charnum, nchars).floatResult(); 00403 } 00404 00405 FloatPoint SVGTextContentElement::getStartPositionOfChar(long charnum, ExceptionCode& ec) const 00406 { 00407 if (charnum < 0 || charnum > getNumberOfChars()) { 00408 ec = DOMException::INDEX_SIZE_ERR; 00409 return FloatPoint(); 00410 } 00411 00412 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::StartPosition, charnum).pointResult(); 00413 } 00414 00415 FloatPoint SVGTextContentElement::getEndPositionOfChar(long charnum, ExceptionCode& ec) const 00416 { 00417 if (charnum < 0 || charnum > getNumberOfChars()) { 00418 ec = DOMException::INDEX_SIZE_ERR; 00419 return FloatPoint(); 00420 } 00421 00422 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::EndPosition, charnum).pointResult(); 00423 } 00424 00425 FloatRect SVGTextContentElement::getExtentOfChar(long charnum, ExceptionCode& ec) const 00426 { 00427 if (charnum < 0 || charnum > getNumberOfChars()) { 00428 ec = DOMException::INDEX_SIZE_ERR; 00429 return FloatRect(); 00430 } 00431 00432 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Extent, charnum).rectResult(); 00433 } 00434 00435 float SVGTextContentElement::getRotationOfChar(long charnum, ExceptionCode& ec) const 00436 { 00437 if (charnum < 0 || charnum > getNumberOfChars()) { 00438 ec = DOMException::INDEX_SIZE_ERR; 00439 return 0.0f; 00440 } 00441 00442 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Rotation, charnum).floatResult(); 00443 } 00444 00445 long SVGTextContentElement::getCharNumAtPosition(const FloatPoint& point) const 00446 { 00447 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::CharacterNumberAtPosition, 0.0f, 0.0f, point).longResult(); 00448 } 00449 00450 void SVGTextContentElement::selectSubString(long charnum, long nchars, ExceptionCode& ec) const 00451 { 00452 long numberOfChars = getNumberOfChars(); 00453 if (charnum < 0 || nchars < 0 || charnum > numberOfChars) { 00454 ec = DOMException::INDEX_SIZE_ERR; 00455 return; 00456 } 00457 00458 if (nchars > numberOfChars - charnum) 00459 nchars = numberOfChars - charnum; 00460 00461 ASSERT(document()); 00462 //khtml ASSERT(document()->frame()); 00463 00464 /*FIXME SelectionController* controller = document()->frame()->selectionController(); 00465 if (!controller) 00466 return; 00467 00468 // Find selection start 00469 VisiblePosition start(const_cast<SVGTextContentElement*>(this), 0, SEL_DEFAULT_AFFINITY); 00470 for (long i = 0; i < charnum; ++i) 00471 start = start.next(); 00472 00473 // Find selection end 00474 VisiblePosition end(start); 00475 for (long i = 0; i < nchars; ++i) 00476 end = end.next(); 00477 00478 controller->setSelection(Selection(start, end));*/ 00479 } 00480 00481 void SVGTextContentElement::parseMappedAttribute(MappedAttribute* attr) 00482 { 00483 if (attr->name() == SVGNames::lengthAdjustAttr) { 00484 if (attr->value() == "spacing") 00485 setLengthAdjustBaseValue(LENGTHADJUST_SPACING); 00486 else if (attr->value() == "spacingAndGlyphs") 00487 setLengthAdjustBaseValue(LENGTHADJUST_SPACINGANDGLYPHS); 00488 } else if (attr->name() == SVGNames::textLengthAttr) { 00489 setTextLengthBaseValue(SVGLength(this, LengthModeOther, attr->value())); 00490 if (textLength().value() < 0.0) 00491 document()->accessSVGExtensions()->reportError("A negative value for text attribute <textLength> is not allowed"); 00492 } else { 00493 if (SVGTests::parseMappedAttribute(attr)) 00494 return; 00495 if (SVGLangSpace::parseMappedAttribute(attr)) { 00496 if (attr->id() == ATTR_XML_SPACE) { 00497 static const DOMString preserveString("preserve"); 00498 00499 if (attr->value() == preserveString) 00500 addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_PRE); 00501 else 00502 addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_NOWRAP); 00503 } 00504 return; 00505 } 00506 if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) 00507 return; 00508 00509 SVGStyledElement::parseMappedAttribute(attr); 00510 } 00511 } 00512 00513 bool SVGTextContentElement::isKnownAttribute(const QualifiedName& attrName) 00514 { 00515 return (attrName.matches(SVGNames::lengthAdjustAttr) || 00516 attrName.matches(SVGNames::textLengthAttr) || 00517 SVGTests::isKnownAttribute(attrName) || 00518 SVGLangSpace::isKnownAttribute(attrName) || 00519 SVGExternalResourcesRequired::isKnownAttribute(attrName) || 00520 SVGStyledElement::isKnownAttribute(attrName)); 00521 } 00522 00523 } 00524 00525 #endif // ENABLE(SVG)
KDE 4.6 API Reference