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