1 /* 2 Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann (at) kde.org> 3 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 23 #if ENABLE(SVG) 24 #include "SVGTextContentElement.h" 25 26 #include "CSSPropertyNames.h" 27 #include "CSSValueKeywords.h" 28 #include "ExceptionCode.h" 29 #include "FloatPoint.h" 30 #include "FloatRect.h" 31 #include "Frame.h" 32 #include "MappedAttribute.h" 33 #include "Position.h" 34 #include "RenderSVGText.h" 35 #include "SVGCharacterLayoutInfo.h" 36 #include "SVGInlineTextBox.h" 37 #include "SVGLength.h" 38 #include "SVGNames.h" 39 #include "SVGRootInlineBox.h" 40 #include "SelectionController.h" 41 #include "XMLNames.h" 42 #include <wtf/StdLibExtras.h> 43 44 namespace WebCore { 45 46 SVGTextContentElement::SVGTextContentElement(const QualifiedName& tagName, Document* doc) 47 : SVGStyledElement(tagName, doc) 48 , SVGTests() 49 , SVGLangSpace() 50 , SVGExternalResourcesRequired() 51 , m_textLength(LengthModeOther) 52 , m_lengthAdjust(LENGTHADJUST_SPACING) 53 { 54 } 55 56 SVGTextContentElement::~SVGTextContentElement() 57 { 58 } 59 60 static inline float cumulativeCharacterRangeLength(const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end, SVGInlineTextBox* textBox, 61 int startOffset, long startPosition, long length, bool isVerticalText, long& atCharacter) 62 { 63 if (!length) 64 return 0.0f; 65 66 float textLength = 0.0f; 67 RenderStyle* style = textBox->textRenderer()->style(); 68 69 bool usesFullRange = (startPosition == -1 && length == -1); 70 71 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 72 if (usesFullRange || (atCharacter >= startPosition && atCharacter <= startPosition + length)) { 73 unsigned int newOffset = textBox->start() + (it - start) + startOffset; 74 75 // Take RTL text into account and pick right glyph width/height. 76 if (textBox->direction() == RTL) 77 newOffset = textBox->start() + textBox->end() - newOffset; 78 79 // FIXME: does this handle multichar glyphs ok? not sure 80 int charsConsumed = 0; 81 String glyphName; 82 if (isVerticalText) 83 textLength += textBox->calculateGlyphHeight(style, newOffset, 0); 84 else 85 textLength += textBox->calculateGlyphWidth(style, newOffset, 0, charsConsumed, glyphName); 86 } 87 88 if (!usesFullRange) { 89 if (atCharacter == startPosition + length - 1) 90 break; 91 92 atCharacter++; 93 } 94 } 95 96 return textLength; 97 } 98 99 // Helper class for querying certain glyph information 100 struct SVGInlineTextBoxQueryWalker { 101 typedef enum { 102 NumberOfCharacters, 103 TextLength, 104 SubStringLength, 105 StartPosition, 106 EndPosition, 107 Extent, 108 Rotation, 109 CharacterNumberAtPosition 110 } QueryMode; 111 112 SVGInlineTextBoxQueryWalker(const SVGTextContentElement* reference, QueryMode mode) 113 : m_reference(reference) 114 , m_mode(mode) 115 , m_queryStartPosition(0) 116 , m_queryLength(0) 117 , m_queryPointInput() 118 , m_queryLongResult(0) 119 , m_queryFloatResult(0.0f) 120 , m_queryPointResult() 121 , m_queryRectResult() 122 , m_stopProcessing(true) 123 , m_atCharacter(0) 124 { 125 } 126 127 void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const AffineTransform&, 128 const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end) 129 { 130 RenderStyle* style = textBox->textRenderer()->style(); 131 bool isVerticalText = style->svgStyle()->writingMode() == WM_TBRL || style->svgStyle()->writingMode() == WM_TB; 132 133 switch (m_mode) { 134 case NumberOfCharacters: 135 { 136 m_queryLongResult += (end - start); 137 m_stopProcessing = false; 138 return; 139 } 140 case TextLength: 141 { 142 float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, -1, -1, isVerticalText, m_atCharacter); 143 144 if (isVerticalText) 145 m_queryFloatResult += textLength; 146 else 147 m_queryFloatResult += textLength; 148 149 m_stopProcessing = false; 150 return; 151 } 152 case SubStringLength: 153 { 154 long startPosition = m_queryStartPosition; 155 long length = m_queryLength; 156 157 float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, startPosition, length, isVerticalText, m_atCharacter); 158 159 if (isVerticalText) 160 m_queryFloatResult += textLength; 161 else 162 m_queryFloatResult += textLength; 163 164 if (m_atCharacter == startPosition + length) 165 m_stopProcessing = true; 166 else 167 m_stopProcessing = false; 168 169 return; 170 } 171 case StartPosition: 172 { 173 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 174 if (m_atCharacter == m_queryStartPosition) { 175 m_queryPointResult = FloatPoint(it->x, it->y); 176 m_stopProcessing = true; 177 return; 178 } 179 180 m_atCharacter++; 181 } 182 183 m_stopProcessing = false; 184 return; 185 } 186 case EndPosition: 187 { 188 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 189 if (m_atCharacter == m_queryStartPosition) { 190 unsigned int newOffset = textBox->start() + (it - start) + startOffset; 191 192 // Take RTL text into account and pick right glyph width/height. 193 if (textBox->direction() == RTL) 194 newOffset = textBox->start() + textBox->end() - newOffset; 195 196 int charsConsumed; 197 String glyphName; 198 // calculateGlyph{Height,Width} will consume at least one character. This is the number of characters available 199 // to them beyond that first one. 200 int extraCharactersAvailable = end - it - 1; 201 if (isVerticalText) 202 m_queryPointResult.move(it->x, it->y + textBox->calculateGlyphHeight(style, newOffset, extraCharactersAvailable)); 203 else 204 m_queryPointResult.move(it->x + textBox->calculateGlyphWidth(style, newOffset, extraCharactersAvailable, charsConsumed, glyphName), it->y); 205 206 m_stopProcessing = true; 207 return; 208 } 209 210 m_atCharacter++; 211 } 212 213 m_stopProcessing = false; 214 return; 215 } 216 case Extent: 217 { 218 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 219 if (m_atCharacter == m_queryStartPosition) { 220 unsigned int newOffset = textBox->start() + (it - start) + startOffset; 221 m_queryRectResult = textBox->calculateGlyphBoundaries(style, newOffset, *it); 222 m_stopProcessing = true; 223 return; 224 } 225 226 m_atCharacter++; 227 } 228 229 m_stopProcessing = false; 230 return; 231 } 232 case Rotation: 233 { 234 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 235 if (m_atCharacter == m_queryStartPosition) { 236 m_queryFloatResult = it->angle; 237 m_stopProcessing = true; 238 return; 239 } 240 241 m_atCharacter++; 242 } 243 244 m_stopProcessing = false; 245 return; 246 } 247 case CharacterNumberAtPosition: 248 { 249 int offset = 0; 250 SVGChar* charAtPos = textBox->closestCharacterToPosition(m_queryPointInput.x(), m_queryPointInput.y(), offset); 251 252 offset += m_atCharacter; 253 if (charAtPos && offset > m_queryLongResult) 254 m_queryLongResult = offset; 255 256 m_atCharacter += end - start; 257 m_stopProcessing = false; 258 return; 259 } 260 default: 261 ASSERT_NOT_REACHED(); 262 m_stopProcessing = true; 263 return; 264 } 265 } 266 267 void setQueryInputParameters(long startPosition, long length, FloatPoint referencePoint) 268 { 269 m_queryStartPosition = startPosition; 270 m_queryLength = length; 271 m_queryPointInput = referencePoint; 272 } 273 274 long longResult() const { return m_queryLongResult; } 275 float floatResult() const { return m_queryFloatResult; } 276 FloatPoint pointResult() const { return m_queryPointResult; } 277 FloatRect rectResult() const { return m_queryRectResult; } 278 bool stopProcessing() const { return m_stopProcessing; } 279 280 private: 281 const SVGTextContentElement* m_reference; 282 QueryMode m_mode; 283 284 long m_queryStartPosition; 285 long m_queryLength; 286 FloatPoint m_queryPointInput; 287 288 long m_queryLongResult; 289 float m_queryFloatResult; 290 FloatPoint m_queryPointResult; 291 FloatRect m_queryRectResult; 292 293 bool m_stopProcessing; 294 long m_atCharacter; 295 }; 296 297 static Vector<SVGInlineTextBox*> findInlineTextBoxInTextChunks(const SVGTextContentElement* element, const Vector<SVGTextChunk>& chunks) 298 { 299 Vector<SVGTextChunk>::const_iterator it = chunks.begin(); 300 const Vector<SVGTextChunk>::const_iterator end = chunks.end(); 301 302 Vector<SVGInlineTextBox*> boxes; 303 304 for (; it != end; ++it) { 305 Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = it->boxes.begin(); 306 const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = it->boxes.end(); 307 308 for (; boxIt != boxEnd; ++boxIt) { 309 SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(boxIt->box); 310 311 Node* textElement = textBox->textRenderer()->parent()->node(); 312 ASSERT(textElement); 313 314 if (textElement == element || textElement->parent() == element) 315 boxes.append(textBox); 316 } 317 } 318 319 return boxes; 320 } 321 322 static inline SVGRootInlineBox* rootInlineBoxForTextContentElement(const SVGTextContentElement* element) 323 { 324 RenderObject* object = element->renderer(); 325 326 if (!object || !object->isSVGText() || object->isText()) 327 return 0; 328 329 RenderBlock* svgText = toRenderBlock(object); 330 331 // Find root inline box 332 SVGRootInlineBox* rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox()); 333 if (!rootBox) { 334 // Layout is not sync yet! 335 element->document()->updateLayoutIgnorePendingStylesheets(); 336 rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox()); 337 } 338 339 ASSERT(rootBox); 340 return rootBox; 341 } 342 343 static inline SVGInlineTextBoxQueryWalker executeTextQuery(const SVGTextContentElement* element, SVGInlineTextBoxQueryWalker::QueryMode mode, 344 long startPosition = 0, long length = 0, FloatPoint referencePoint = FloatPoint()) 345 { 346 SVGRootInlineBox* rootBox = rootInlineBoxForTextContentElement(element); 347 if (!rootBox) 348 return SVGInlineTextBoxQueryWalker(0, mode); 349 350 // Find all inline text box associated with our renderer 351 Vector<SVGInlineTextBox*> textBoxes = findInlineTextBoxInTextChunks(element, rootBox->svgTextChunks()); 352 353 // Walk text chunks to find chunks associated with our inline text box 354 SVGInlineTextBoxQueryWalker walkerCallback(element, mode); 355 walkerCallback.setQueryInputParameters(startPosition, length, referencePoint); 356 357 SVGTextChunkWalker<SVGInlineTextBoxQueryWalker> walker(&walkerCallback, &SVGInlineTextBoxQueryWalker::chunkPortionCallback); 358 359 Vector<SVGInlineTextBox*>::iterator it = textBoxes.begin(); 360 Vector<SVGInlineTextBox*>::iterator end = textBoxes.end(); 361 362 for (; it != end; ++it) { 363 rootBox->walkTextChunks(&walker, *it); 364 365 if (walkerCallback.stopProcessing()) 366 break; 367 } 368 369 return walkerCallback; 370 } 371 372 unsigned SVGTextContentElement::getNumberOfChars() const 373 { 374 document()->updateLayoutIgnorePendingStylesheets(); 375 376 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::NumberOfCharacters).longResult(); 377 } 378 379 float SVGTextContentElement::getComputedTextLength() const 380 { 381 document()->updateLayoutIgnorePendingStylesheets(); 382 383 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::TextLength).floatResult(); 384 } 385 386 float SVGTextContentElement::getSubStringLength(unsigned charnum, unsigned nchars, ExceptionCode& ec) const 387 { 388 document()->updateLayoutIgnorePendingStylesheets(); 389 390 unsigned numberOfChars = getNumberOfChars(); 391 if (charnum >= numberOfChars) { 392 ec = INDEX_SIZE_ERR; 393 return 0.0f; 394 } 395 396 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::SubStringLength, charnum, nchars).floatResult(); 397 } 398 399 FloatPoint SVGTextContentElement::getStartPositionOfChar(unsigned charnum, ExceptionCode& ec) const 400 { 401 document()->updateLayoutIgnorePendingStylesheets(); 402 403 if (charnum > getNumberOfChars()) { 404 ec = INDEX_SIZE_ERR; 405 return FloatPoint(); 406 } 407 408 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::StartPosition, charnum).pointResult(); 409 } 410 411 FloatPoint SVGTextContentElement::getEndPositionOfChar(unsigned charnum, ExceptionCode& ec) const 412 { 413 document()->updateLayoutIgnorePendingStylesheets(); 414 415 if (charnum > getNumberOfChars()) { 416 ec = INDEX_SIZE_ERR; 417 return FloatPoint(); 418 } 419 420 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::EndPosition, charnum).pointResult(); 421 } 422 423 FloatRect SVGTextContentElement::getExtentOfChar(unsigned charnum, ExceptionCode& ec) const 424 { 425 document()->updateLayoutIgnorePendingStylesheets(); 426 427 if (charnum > getNumberOfChars()) { 428 ec = INDEX_SIZE_ERR; 429 return FloatRect(); 430 } 431 432 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Extent, charnum).rectResult(); 433 } 434 435 float SVGTextContentElement::getRotationOfChar(unsigned charnum, ExceptionCode& ec) const 436 { 437 document()->updateLayoutIgnorePendingStylesheets(); 438 439 if (charnum > getNumberOfChars()) { 440 ec = INDEX_SIZE_ERR; 441 return 0.0f; 442 } 443 444 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Rotation, charnum).floatResult(); 445 } 446 447 int SVGTextContentElement::getCharNumAtPosition(const FloatPoint& point) const 448 { 449 document()->updateLayoutIgnorePendingStylesheets(); 450 451 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::CharacterNumberAtPosition, 0.0f, 0.0f, point).longResult(); 452 } 453 454 void SVGTextContentElement::selectSubString(unsigned charnum, unsigned nchars, ExceptionCode& ec) const 455 { 456 unsigned numberOfChars = getNumberOfChars(); 457 if (charnum >= numberOfChars) { 458 ec = INDEX_SIZE_ERR; 459 return; 460 } 461 462 if (nchars > numberOfChars - charnum) 463 nchars = numberOfChars - charnum; 464 465 ASSERT(document()); 466 ASSERT(document()->frame()); 467 468 SelectionController* controller = document()->frame()->selection(); 469 if (!controller) 470 return; 471 472 // Find selection start 473 VisiblePosition start(const_cast<SVGTextContentElement*>(this), 0, SEL_DEFAULT_AFFINITY); 474 for (unsigned i = 0; i < charnum; ++i) 475 start = start.next(); 476 477 // Find selection end 478 VisiblePosition end(start); 479 for (unsigned i = 0; i < nchars; ++i) 480 end = end.next(); 481 482 controller->setSelection(VisibleSelection(start, end)); 483 } 484 485 void SVGTextContentElement::parseMappedAttribute(MappedAttribute* attr) 486 { 487 if (attr->name() == SVGNames::lengthAdjustAttr) { 488 if (attr->value() == "spacing") 489 setLengthAdjustBaseValue(LENGTHADJUST_SPACING); 490 else if (attr->value() == "spacingAndGlyphs") 491 setLengthAdjustBaseValue(LENGTHADJUST_SPACINGANDGLYPHS); 492 } else if (attr->name() == SVGNames::textLengthAttr) { 493 setTextLengthBaseValue(SVGLength(LengthModeOther, attr->value())); 494 if (textLengthBaseValue().value(this) < 0.0) 495 document()->accessSVGExtensions()->reportError("A negative value for text attribute <textLength> is not allowed"); 496 } else { 497 if (SVGTests::parseMappedAttribute(attr)) 498 return; 499 if (SVGLangSpace::parseMappedAttribute(attr)) { 500 if (attr->name().matches(XMLNames::spaceAttr)) { 501 DEFINE_STATIC_LOCAL(const AtomicString, preserveString, ("preserve")); 502 503 if (attr->value() == preserveString) 504 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValuePre); 505 else 506 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValueNowrap); 507 } 508 return; 509 } 510 if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) 511 return; 512 513 SVGStyledElement::parseMappedAttribute(attr); 514 } 515 } 516 517 void SVGTextContentElement::synchronizeProperty(const QualifiedName& attrName) 518 { 519 SVGStyledElement::synchronizeProperty(attrName); 520 521 if (attrName == anyQName()) { 522 synchronizeLengthAdjust(); 523 synchronizeTextLength(); 524 synchronizeExternalResourcesRequired(); 525 return; 526 } 527 528 if (attrName == SVGNames::lengthAdjustAttr) 529 synchronizeLengthAdjust(); 530 else if (attrName == SVGNames::textLengthAttr) 531 synchronizeTextLength(); 532 else if (SVGExternalResourcesRequired::isKnownAttribute(attrName)) 533 synchronizeExternalResourcesRequired(); 534 } 535 536 bool SVGTextContentElement::isKnownAttribute(const QualifiedName& attrName) 537 { 538 return (attrName.matches(SVGNames::lengthAdjustAttr) || 539 attrName.matches(SVGNames::textLengthAttr) || 540 SVGTests::isKnownAttribute(attrName) || 541 SVGLangSpace::isKnownAttribute(attrName) || 542 SVGExternalResourcesRequired::isKnownAttribute(attrName) || 543 SVGStyledElement::isKnownAttribute(attrName)); 544 } 545 546 } 547 548 #endif // ENABLE(SVG) 549