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/RenderBlockFlow.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 32 namespace blink { 33 34 // Base structure for callback user data 35 struct 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 50 static 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 RenderBlockFlow* renderBlockFlow = toRenderBlockFlow(renderer); 59 60 // RenderSVGText only ever contains a single line box. 61 InlineFlowBox* flowBox = renderBlockFlow->firstLineBox(); 62 ASSERT(flowBox == renderBlockFlow->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 80 SVGTextQuery::SVGTextQuery(RenderObject* renderer) 81 { 82 collectTextBoxesInFlowBox(flowBoxForRenderer(renderer)); 83 } 84 85 void 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 105 bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextFragmentCallback fragmentCallback) const 106 { 107 unsigned processedCharacters = 0; 108 unsigned textBoxCount = m_textBoxes.size(); 109 110 // Loop over all text boxes 111 for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) { 112 queryData->textBox = m_textBoxes.at(textBoxPosition); 113 queryData->textRenderer = &toRenderSVGInlineText(queryData->textBox->renderer()); 114 ASSERT(queryData->textRenderer->style()); 115 116 queryData->isVerticalText = queryData->textRenderer->style()->svgStyle().isVerticalWritingMode(); 117 const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments(); 118 119 // Loop over all text fragments in this text box, firing a callback for each. 120 unsigned fragmentCount = fragments.size(); 121 for (unsigned i = 0; i < fragmentCount; ++i) { 122 const SVGTextFragment& fragment = fragments.at(i); 123 if ((this->*fragmentCallback)(queryData, fragment)) 124 return true; 125 126 processedCharacters += fragment.length; 127 } 128 129 queryData->processedCharacters = processedCharacters; 130 } 131 132 return false; 133 } 134 135 bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const 136 { 137 // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text fragment. 138 startPosition -= queryData->processedCharacters; 139 endPosition -= queryData->processedCharacters; 140 141 // <startPosition, endPosition> is now a tuple of offsets relative to the current text box. 142 // Compute the offsets of the fragment in the same offset space. 143 int fragmentStartInBox = fragment.characterOffset - queryData->textBox->start(); 144 int fragmentEndInBox = fragmentStartInBox + fragment.length; 145 146 // Check if the ranges intersect. 147 startPosition = std::max(startPosition, fragmentStartInBox); 148 endPosition = std::min(endPosition, fragmentEndInBox); 149 150 if (startPosition >= endPosition) 151 return false; 152 153 modifyStartEndPositionsRespectingLigatures(queryData, fragment, startPosition, endPosition); 154 if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition)) 155 return false; 156 157 ASSERT(startPosition < endPosition); 158 return true; 159 } 160 161 void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const 162 { 163 SVGTextLayoutAttributes* layoutAttributes = queryData->textRenderer->layoutAttributes(); 164 Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes->textMetricsValues(); 165 166 unsigned textMetricsOffset = fragment.metricsListOffset; 167 168 // Compute the offset of the fragment within the box, since that's the 169 // space <startPosition, endPosition> is in (and that's what we need). 170 int fragmentOffsetInBox = fragment.characterOffset - queryData->textBox->start(); 171 int fragmentEndInBox = fragmentOffsetInBox + fragment.length; 172 173 // Find the text metrics cell that start at or contain the character startPosition. 174 while (fragmentOffsetInBox < fragmentEndInBox) { 175 SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset]; 176 int glyphEnd = fragmentOffsetInBox + metrics.length(); 177 if (startPosition < glyphEnd) 178 break; 179 fragmentOffsetInBox = glyphEnd; 180 textMetricsOffset++; 181 } 182 183 startPosition = fragmentOffsetInBox; 184 185 // Find the text metrics cell that contain or ends at the character endPosition. 186 while (fragmentOffsetInBox < fragmentEndInBox) { 187 SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset]; 188 fragmentOffsetInBox += metrics.length(); 189 if (fragmentOffsetInBox >= endPosition) 190 break; 191 textMetricsOffset++; 192 } 193 194 endPosition = fragmentOffsetInBox; 195 } 196 197 // numberOfCharacters() implementation 198 bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const 199 { 200 // no-op 201 return false; 202 } 203 204 unsigned SVGTextQuery::numberOfCharacters() const 205 { 206 Data data; 207 executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback); 208 return data.processedCharacters; 209 } 210 211 // textLength() implementation 212 struct TextLengthData : SVGTextQuery::Data { 213 TextLengthData() 214 : textLength(0) 215 { 216 } 217 218 float textLength; 219 }; 220 221 bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const 222 { 223 TextLengthData* data = static_cast<TextLengthData*>(queryData); 224 data->textLength += queryData->isVerticalText ? fragment.height : fragment.width; 225 return false; 226 } 227 228 float SVGTextQuery::textLength() const 229 { 230 TextLengthData data; 231 executeQuery(&data, &SVGTextQuery::textLengthCallback); 232 return data.textLength; 233 } 234 235 // subStringLength() implementation 236 struct SubStringLengthData : SVGTextQuery::Data { 237 SubStringLengthData(unsigned queryStartPosition, unsigned queryLength) 238 : startPosition(queryStartPosition) 239 , length(queryLength) 240 , subStringLength(0) 241 { 242 } 243 244 unsigned startPosition; 245 unsigned length; 246 247 float subStringLength; 248 }; 249 250 bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const 251 { 252 SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData); 253 254 int startPosition = data->startPosition; 255 int endPosition = startPosition + data->length; 256 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 257 return false; 258 259 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, endPosition - startPosition); 260 data->subStringLength += queryData->isVerticalText ? metrics.height() : metrics.width(); 261 return false; 262 } 263 264 float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const 265 { 266 SubStringLengthData data(startPosition, length); 267 executeQuery(&data, &SVGTextQuery::subStringLengthCallback); 268 return data.subStringLength; 269 } 270 271 // startPositionOfCharacter() implementation 272 struct StartPositionOfCharacterData : SVGTextQuery::Data { 273 StartPositionOfCharacterData(unsigned queryPosition) 274 : position(queryPosition) 275 { 276 } 277 278 unsigned position; 279 FloatPoint startPosition; 280 }; 281 282 bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const 283 { 284 StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData); 285 286 int startPosition = data->position; 287 int endPosition = startPosition + 1; 288 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 289 return false; 290 291 data->startPosition = FloatPoint(fragment.x, fragment.y); 292 293 if (startPosition) { 294 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition); 295 if (queryData->isVerticalText) 296 data->startPosition.move(0, metrics.height()); 297 else 298 data->startPosition.move(metrics.width(), 0); 299 } 300 301 AffineTransform fragmentTransform; 302 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength); 303 if (fragmentTransform.isIdentity()) 304 return true; 305 306 data->startPosition = fragmentTransform.mapPoint(data->startPosition); 307 return true; 308 } 309 310 FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const 311 { 312 StartPositionOfCharacterData data(position); 313 executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback); 314 return data.startPosition; 315 } 316 317 // endPositionOfCharacter() implementation 318 struct EndPositionOfCharacterData : SVGTextQuery::Data { 319 EndPositionOfCharacterData(unsigned queryPosition) 320 : position(queryPosition) 321 { 322 } 323 324 unsigned position; 325 FloatPoint endPosition; 326 }; 327 328 bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const 329 { 330 EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData); 331 332 int startPosition = data->position; 333 int endPosition = startPosition + 1; 334 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 335 return false; 336 337 data->endPosition = FloatPoint(fragment.x, fragment.y); 338 339 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition + 1); 340 if (queryData->isVerticalText) 341 data->endPosition.move(0, metrics.height()); 342 else 343 data->endPosition.move(metrics.width(), 0); 344 345 AffineTransform fragmentTransform; 346 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength); 347 if (fragmentTransform.isIdentity()) 348 return true; 349 350 data->endPosition = fragmentTransform.mapPoint(data->endPosition); 351 return true; 352 } 353 354 FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const 355 { 356 EndPositionOfCharacterData data(position); 357 executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback); 358 return data.endPosition; 359 } 360 361 // rotationOfCharacter() implementation 362 struct RotationOfCharacterData : SVGTextQuery::Data { 363 RotationOfCharacterData(unsigned queryPosition) 364 : position(queryPosition) 365 , rotation(0) 366 { 367 } 368 369 unsigned position; 370 float rotation; 371 }; 372 373 bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const 374 { 375 RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData); 376 377 int startPosition = data->position; 378 int endPosition = startPosition + 1; 379 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 380 return false; 381 382 AffineTransform fragmentTransform; 383 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength); 384 if (fragmentTransform.isIdentity()) 385 data->rotation = 0; 386 else { 387 fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale()); 388 data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a()))); 389 } 390 391 return true; 392 } 393 394 float SVGTextQuery::rotationOfCharacter(unsigned position) const 395 { 396 RotationOfCharacterData data(position); 397 executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback); 398 return data.rotation; 399 } 400 401 // extentOfCharacter() implementation 402 struct ExtentOfCharacterData : SVGTextQuery::Data { 403 ExtentOfCharacterData(unsigned queryPosition) 404 : position(queryPosition) 405 { 406 } 407 408 unsigned position; 409 FloatRect extent; 410 }; 411 412 static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent) 413 { 414 float scalingFactor = queryData->textRenderer->scalingFactor(); 415 ASSERT(scalingFactor); 416 417 extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->scaledFont().fontMetrics().floatAscent() / scalingFactor)); 418 419 if (startPosition) { 420 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition); 421 if (queryData->isVerticalText) 422 extent.move(0, metrics.height()); 423 else 424 extent.move(metrics.width(), 0); 425 } 426 427 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, 1); 428 extent.setSize(FloatSize(metrics.width(), metrics.height())); 429 430 AffineTransform fragmentTransform; 431 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength); 432 433 extent = fragmentTransform.mapRect(extent); 434 } 435 436 static inline FloatRect calculateFragmentBoundaries(const RenderSVGInlineText& textRenderer, const SVGTextFragment& fragment) 437 { 438 float scalingFactor = textRenderer.scalingFactor(); 439 ASSERT(scalingFactor); 440 float baseline = textRenderer.scaledFont().fontMetrics().floatAscent() / scalingFactor; 441 442 AffineTransform fragmentTransform; 443 FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); 444 fragment.buildFragmentTransform(fragmentTransform); 445 return fragmentTransform.mapRect(fragmentRect); 446 } 447 448 bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const 449 { 450 ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData); 451 452 int startPosition = data->position; 453 int endPosition = startPosition + 1; 454 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) 455 return false; 456 457 calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent); 458 return true; 459 } 460 461 FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const 462 { 463 ExtentOfCharacterData data(position); 464 executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback); 465 return data.extent; 466 } 467 468 // characterNumberAtPosition() implementation 469 struct CharacterNumberAtPositionData : SVGTextQuery::Data { 470 CharacterNumberAtPositionData(const FloatPoint& queryPosition) 471 : position(queryPosition) 472 { 473 } 474 475 FloatPoint position; 476 }; 477 478 bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const 479 { 480 CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData); 481 482 // Test the query point against the bounds of the entire fragment first. 483 FloatRect fragmentExtents = calculateFragmentBoundaries(*queryData->textRenderer, fragment); 484 if (!fragmentExtents.contains(data->position)) 485 return false; 486 487 // Iterate through the glyphs in this fragment, and check if their extents 488 // contain the query point. 489 FloatRect extent; 490 const Vector<SVGTextMetrics>& textMetrics = queryData->textRenderer->layoutAttributes()->textMetricsValues(); 491 unsigned textMetricsOffset = fragment.metricsListOffset; 492 unsigned fragmentOffset = 0; 493 while (fragmentOffset < fragment.length) { 494 calculateGlyphBoundaries(queryData, fragment, fragmentOffset, extent); 495 if (extent.contains(data->position)) { 496 // Compute the character offset of the glyph within the text box 497 // and add to processedCharacters. 498 unsigned characterOffset = fragment.characterOffset + fragmentOffset; 499 data->processedCharacters += characterOffset - data->textBox->start(); 500 return true; 501 } 502 fragmentOffset += textMetrics[textMetricsOffset].length(); 503 textMetricsOffset++; 504 } 505 return false; 506 } 507 508 int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const 509 { 510 CharacterNumberAtPositionData data(position); 511 if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback)) 512 return -1; 513 514 return data.processedCharacters; 515 } 516 517 } 518