1 /* 2 * Copyright (C) Research In Motion Limited 2010. All rights reserved. 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Library General Public 6 * License as published by the Free Software Foundation; either 7 * version 2 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Library General Public License for more details. 13 * 14 * You should have received a copy of the GNU Library General Public License 15 * along with this library; see the file COPYING.LIB. If not, write to 16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17 * Boston, MA 02110-1301, USA. 18 */ 19 20 #include "config.h" 21 #include "SVGTextQuery.h" 22 23 #if ENABLE(SVG) 24 #include "FloatConversion.h" 25 #include "InlineFlowBox.h" 26 #include "RenderBlock.h" 27 #include "RenderInline.h" 28 #include "RenderSVGInlineText.h" 29 #include "SVGInlineTextBox.h" 30 #include "SVGTextMetrics.h" 31 #include "VisiblePosition.h" 32 33 #include <wtf/MathExtras.h> 34 35 namespace WebCore { 36 37 // Base structure for callback user data 38 struct SVGTextQuery::Data { 39 Data() 40 : isVerticalText(false) 41 , processedCharacters(0) 42 , textRenderer(0) 43 , textBox(0) 44 { 45 } 46 47 bool isVerticalText; 48 unsigned processedCharacters; 49 RenderSVGInlineText* textRenderer; 50 const SVGInlineTextBox* textBox; 51 }; 52 53 static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer) 54 { 55 if (!renderer) 56 return 0; 57 58 if (renderer->isRenderBlock()) { 59 // If we're given a block element, it has to be a RenderSVGText. 60 ASSERT(renderer->isSVGText()); 61 RenderBlock* renderBlock = toRenderBlock(renderer); 62 63 // RenderSVGText only ever contains a single line box. 64 InlineFlowBox* flowBox = renderBlock->firstLineBox(); 65 ASSERT(flowBox == renderBlock->lastLineBox()); 66 return flowBox; 67 } 68 69 if (renderer->isRenderInline()) { 70 // We're given a RenderSVGInline or objects that derive from it (RenderSVGTSpan / RenderSVGTextPath) 71 RenderInline* renderInline = toRenderInline(renderer); 72 73 // RenderSVGInline only ever contains a single line box. 74 InlineFlowBox* flowBox = renderInline->firstLineBox(); 75 ASSERT(flowBox == renderInline->lastLineBox()); 76 return flowBox; 77 } 78 79 ASSERT_NOT_REACHED(); 80 return 0; 81 } 82 83 SVGTextQuery::SVGTextQuery(RenderObject* renderer) 84 { 85 collectTextBoxesInFlowBox(flowBoxForRenderer(renderer)); 86 } 87 88 void SVGTextQuery::collectTextBoxesInFlowBox(InlineFlowBox* flowBox) 89 { 90 if (!flowBox) 91 return; 92 93 for (InlineBox* child = flowBox->firstChild(); child; child = child->nextOnLine()) { 94 if (child->isInlineFlowBox()) { 95 // Skip generated content. 96 if (!child->renderer()->node()) 97 continue; 98 99 collectTextBoxesInFlowBox(static_cast<InlineFlowBox*>(child)); 100 continue; 101 } 102 103 if (child->isSVGInlineTextBox()) 104 m_textBoxes.append(static_cast<SVGInlineTextBox*>(child)); 105 } 106 } 107 108 bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextFragmentCallback fragmentCallback) const 109 { 110 ASSERT(!m_textBoxes.isEmpty()); 111 112 unsigned processedCharacters = 0; 113 unsigned textBoxCount = m_textBoxes.size(); 114 115 // Loop over all text boxes 116 for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) { 117 queryData->textBox = m_textBoxes.at(textBoxPosition); 118 queryData->textRenderer = toRenderSVGInlineText(queryData->textBox->textRenderer()); 119 ASSERT(queryData->textRenderer); 120 ASSERT(queryData->textRenderer->style()); 121 ASSERT(queryData->textRenderer->style()->svgStyle()); 122 123 queryData->isVerticalText = queryData->textRenderer->style()->svgStyle()->isVerticalWritingMode(); 124 const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments(); 125 126 // Loop over all text fragments in this text box, firing a callback for each. 127 unsigned fragmentCount = fragments.size(); 128 for (unsigned i = 0; i < fragmentCount; ++i) { 129 const SVGTextFragment& fragment = fragments.at(i); 130 if ((this->*fragmentCallback)(queryData, fragment)) 131 return true; 132 133 processedCharacters += fragment.length; 134 } 135 136 queryData->processedCharacters = processedCharacters; 137 } 138 139 return false; 140 } 141 142 bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const 143 { 144 // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text fragment. 145 startPosition -= queryData->processedCharacters; 146 endPosition -= queryData->processedCharacters; 147 148 if (startPosition >= endPosition || startPosition < 0 || endPosition < 0) 149 return false; 150 151 modifyStartEndPositionsRespectingLigatures(queryData, startPosition, endPosition); 152 if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition)) 153 return false; 154 155 ASSERT(startPosition < endPosition); 156 return true; 157 } 158 159 void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, int& startPosition, int& endPosition) const 160 { 161 const SVGTextLayoutAttributes& layoutAttributes = queryData->textRenderer->layoutAttributes(); 162 const Vector<float>& xValues = layoutAttributes.xValues(); 163 const Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes.textMetricsValues(); 164 165 unsigned boxStart = queryData->textBox->start(); 166 unsigned boxLength = queryData->textBox->len(); 167 168 unsigned textMetricsOffset = 0; 169 unsigned textMetricsSize = textMetricsValues.size(); 170 171 unsigned positionOffset = 0; 172 unsigned positionSize = xValues.size(); 173 174 bool alterStartPosition = true; 175 bool alterEndPosition = true; 176 177 int lastPositionOffset = -1; 178 for (; textMetricsOffset < textMetricsSize && positionOffset < positionSize; ++textMetricsOffset) { 179 const SVGTextMetrics& metrics = textMetricsValues.at(textMetricsOffset); 180 181 // Advance to text box start location. 182 if (positionOffset < boxStart) { 183 positionOffset += metrics.length(); 184 continue; 185 } 186 187 // Stop if we've finished processing this text box. 188 if (positionOffset >= boxStart + boxLength) 189 break; 190 191 // If the start position maps to a character in the metrics list, we don't need to modify it. 192 if (startPosition == static_cast<int>(positionOffset)) 193 alterStartPosition = false; 194 195 // If the start position maps to a character in the metrics list, we don't need to modify it. 196 if (endPosition == static_cast<int>(positionOffset)) 197 alterEndPosition = false; 198 199 // Detect ligatures. 200 if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) { 201 if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) { 202 startPosition = lastPositionOffset; 203 alterStartPosition = false; 204 } 205 206 if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) { 207 endPosition = positionOffset; 208 alterEndPosition = false; 209 } 210 } 211 212 if (!alterStartPosition && !alterEndPosition) 213 break; 214 215 lastPositionOffset = positionOffset; 216 positionOffset += metrics.length(); 217 } 218 219 if (!alterStartPosition && !alterEndPosition) 220 return; 221 222 if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) { 223 if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) { 224 startPosition = lastPositionOffset; 225 alterStartPosition = false; 226 } 227 228 if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) { 229 endPosition = positionOffset; 230 alterEndPosition = false; 231 } 232 } 233 } 234 235 // numberOfCharacters() implementation 236 bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const 237 { 238 // no-op 239 return false; 240 } 241 242 unsigned SVGTextQuery::numberOfCharacters() const 243 { 244 if (m_textBoxes.isEmpty()) 245 return 0; 246 247 Data data; 248 executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback); 249 return data.processedCharacters; 250 } 251 252 // textLength() implementation 253 struct TextLengthData : SVGTextQuery::Data { 254 TextLengthData() 255 : textLength(0) 256 { 257 } 258 259 float textLength; 260 }; 261 262 bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const 263 { 264 TextLengthData* data = static_cast<TextLengthData*>(queryData); 265 data->textLength += queryData->isVerticalText ? fragment.height : fragment.width; 266 return false; 267 } 268 269 float SVGTextQuery::textLength() const 270 { 271 if (m_textBoxes.isEmpty()) 272 return 0; 273 274 TextLengthData data; 275 executeQuery(&data, &SVGTextQuery::textLengthCallback); 276 return data.textLength; 277 } 278 279 // subStringLength() implementation 280 struct SubStringLengthData : SVGTextQuery::Data { 281 SubStringLengthData(unsigned queryStartPosition, unsigned queryLength) 282 : startPosition(queryStartPosition) 283 , length(queryLength) 284 , subStringLength(0) 285 { 286 } 287 288 unsigned startPosition; 289 unsigned length; 290 291 float subStringLength; 292 }; 293 294 bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const 295 { 296 SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData); 297 298 int startPosition = data->startPosition; 299 int endPosition = startPosition + data->length; 300 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 301 return false; 302 303 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, endPosition - startPosition); 304 data->subStringLength += queryData->isVerticalText ? metrics.height() : metrics.width(); 305 return false; 306 } 307 308 float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const 309 { 310 if (m_textBoxes.isEmpty()) 311 return 0; 312 313 SubStringLengthData data(startPosition, length); 314 executeQuery(&data, &SVGTextQuery::subStringLengthCallback); 315 return data.subStringLength; 316 } 317 318 // startPositionOfCharacter() implementation 319 struct StartPositionOfCharacterData : SVGTextQuery::Data { 320 StartPositionOfCharacterData(unsigned queryPosition) 321 : position(queryPosition) 322 { 323 } 324 325 unsigned position; 326 FloatPoint startPosition; 327 }; 328 329 bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const 330 { 331 StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData); 332 333 int startPosition = data->position; 334 int endPosition = startPosition + 1; 335 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 336 return false; 337 338 data->startPosition = FloatPoint(fragment.x, fragment.y); 339 340 if (startPosition) { 341 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition); 342 if (queryData->isVerticalText) 343 data->startPosition.move(0, metrics.height()); 344 else 345 data->startPosition.move(metrics.width(), 0); 346 } 347 348 AffineTransform fragmentTransform; 349 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength); 350 if (fragmentTransform.isIdentity()) 351 return true; 352 353 data->startPosition = fragmentTransform.mapPoint(data->startPosition); 354 return true; 355 } 356 357 FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const 358 { 359 if (m_textBoxes.isEmpty()) 360 return FloatPoint(); 361 362 StartPositionOfCharacterData data(position); 363 executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback); 364 return data.startPosition; 365 } 366 367 // endPositionOfCharacter() implementation 368 struct EndPositionOfCharacterData : SVGTextQuery::Data { 369 EndPositionOfCharacterData(unsigned queryPosition) 370 : position(queryPosition) 371 { 372 } 373 374 unsigned position; 375 FloatPoint endPosition; 376 }; 377 378 bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const 379 { 380 EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData); 381 382 int startPosition = data->position; 383 int endPosition = startPosition + 1; 384 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 385 return false; 386 387 data->endPosition = FloatPoint(fragment.x, fragment.y); 388 389 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition + 1); 390 if (queryData->isVerticalText) 391 data->endPosition.move(0, metrics.height()); 392 else 393 data->endPosition.move(metrics.width(), 0); 394 395 AffineTransform fragmentTransform; 396 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength); 397 if (fragmentTransform.isIdentity()) 398 return true; 399 400 data->endPosition = fragmentTransform.mapPoint(data->endPosition); 401 return true; 402 } 403 404 FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const 405 { 406 if (m_textBoxes.isEmpty()) 407 return FloatPoint(); 408 409 EndPositionOfCharacterData data(position); 410 executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback); 411 return data.endPosition; 412 } 413 414 // rotationOfCharacter() implementation 415 struct RotationOfCharacterData : SVGTextQuery::Data { 416 RotationOfCharacterData(unsigned queryPosition) 417 : position(queryPosition) 418 , rotation(0) 419 { 420 } 421 422 unsigned position; 423 float rotation; 424 }; 425 426 bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const 427 { 428 RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData); 429 430 int startPosition = data->position; 431 int endPosition = startPosition + 1; 432 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 433 return false; 434 435 AffineTransform fragmentTransform; 436 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength); 437 if (fragmentTransform.isIdentity()) 438 data->rotation = 0; 439 else { 440 fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale()); 441 data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a()))); 442 } 443 444 return true; 445 } 446 447 float SVGTextQuery::rotationOfCharacter(unsigned position) const 448 { 449 if (m_textBoxes.isEmpty()) 450 return 0; 451 452 RotationOfCharacterData data(position); 453 executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback); 454 return data.rotation; 455 } 456 457 // extentOfCharacter() implementation 458 struct ExtentOfCharacterData : SVGTextQuery::Data { 459 ExtentOfCharacterData(unsigned queryPosition) 460 : position(queryPosition) 461 { 462 } 463 464 unsigned position; 465 FloatRect extent; 466 }; 467 468 static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent) 469 { 470 float scalingFactor = queryData->textRenderer->scalingFactor(); 471 ASSERT(scalingFactor); 472 473 extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->scaledFont().fontMetrics().floatAscent() / scalingFactor)); 474 475 if (startPosition) { 476 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition); 477 if (queryData->isVerticalText) 478 extent.move(0, metrics.height()); 479 else 480 extent.move(metrics.width(), 0); 481 } 482 483 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, 1); 484 extent.setSize(FloatSize(metrics.width(), metrics.height())); 485 486 AffineTransform fragmentTransform; 487 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength); 488 if (fragmentTransform.isIdentity()) 489 return; 490 491 extent = fragmentTransform.mapRect(extent); 492 } 493 494 bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const 495 { 496 ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData); 497 498 int startPosition = data->position; 499 int endPosition = startPosition + 1; 500 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 501 return false; 502 503 calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent); 504 return true; 505 } 506 507 FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const 508 { 509 if (m_textBoxes.isEmpty()) 510 return FloatRect(); 511 512 ExtentOfCharacterData data(position); 513 executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback); 514 return data.extent; 515 } 516 517 // characterNumberAtPosition() implementation 518 struct CharacterNumberAtPositionData : SVGTextQuery::Data { 519 CharacterNumberAtPositionData(const FloatPoint& queryPosition) 520 : position(queryPosition) 521 { 522 } 523 524 FloatPoint position; 525 }; 526 527 bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const 528 { 529 CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData); 530 531 FloatRect extent; 532 for (unsigned i = 0; i < fragment.length; ++i) { 533 int startPosition = data->processedCharacters + i; 534 int endPosition = startPosition + 1; 535 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 536 continue; 537 538 calculateGlyphBoundaries(queryData, fragment, startPosition, extent); 539 if (extent.contains(data->position)) { 540 data->processedCharacters += i; 541 return true; 542 } 543 } 544 545 return false; 546 } 547 548 int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const 549 { 550 if (m_textBoxes.isEmpty()) 551 return -1; 552 553 CharacterNumberAtPositionData data(position); 554 if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback)) 555 return -1; 556 557 return data.processedCharacters; 558 } 559 560 } 561 562 #endif 563