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