2 * Copyright (C) Research In Motion Limited 2010-2012. All rights reserved.
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.
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.
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.
21 #include "SVGTextQuery.h"
23 #include "FloatConversion.h"
24 #include "InlineFlowBox.h"
25 #include "RenderBlockFlow.h"
26 #include "RenderInline.h"
27 #include "SVGInlineTextBox.h"
28 #include "VisiblePosition.h"
30 #include <wtf/MathExtras.h>
34 // Base structure for callback user data
35 struct SVGTextQuery::Data {
37 : isVerticalText(false)
38 , processedCharacters(0)
45 unsigned processedCharacters;
46 RenderSVGInlineText* textRenderer;
47 const SVGInlineTextBox* textBox;
50 static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer)
55 if (renderer->isRenderBlockFlow()) {
56 // If we're given a block element, it has to be a RenderSVGText.
57 ASSERT(renderer->isSVGText());
58 RenderBlockFlow& renderBlock = toRenderBlockFlow(*renderer);
60 // RenderSVGText only ever contains a single line box.
61 auto flowBox = renderBlock.firstRootBox();
62 ASSERT(flowBox == renderBlock.lastRootBox());
66 if (renderer->isRenderInline()) {
67 // We're given a RenderSVGInline or objects that derive from it (RenderSVGTSpan / RenderSVGTextPath)
68 RenderInline& renderInline = toRenderInline(*renderer);
70 // RenderSVGInline only ever contains a single line box.
71 InlineFlowBox* flowBox = renderInline.firstLineBox();
72 ASSERT(flowBox == renderInline.lastLineBox());
80 SVGTextQuery::SVGTextQuery(RenderObject* renderer)
82 collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
85 void SVGTextQuery::collectTextBoxesInFlowBox(InlineFlowBox* flowBox)
90 for (InlineBox* child = flowBox->firstChild(); child; child = child->nextOnLine()) {
91 if (child->isInlineFlowBox()) {
92 // Skip generated content.
93 if (!child->renderer().node())
96 collectTextBoxesInFlowBox(toInlineFlowBox(child));
100 if (child->isSVGInlineTextBox())
101 m_textBoxes.append(toSVGInlineTextBox(child));
105 bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextFragmentCallback fragmentCallback) const
107 ASSERT(!m_textBoxes.isEmpty());
109 unsigned processedCharacters = 0;
110 unsigned textBoxCount = m_textBoxes.size();
112 // Loop over all text boxes
113 for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) {
114 queryData->textBox = m_textBoxes.at(textBoxPosition);
115 queryData->textRenderer = &queryData->textBox->renderer();
117 queryData->isVerticalText = queryData->textRenderer->style().svgStyle().isVerticalWritingMode();
118 const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments();
120 // Loop over all text fragments in this text box, firing a callback for each.
121 unsigned fragmentCount = fragments.size();
122 for (unsigned i = 0; i < fragmentCount; ++i) {
123 const SVGTextFragment& fragment = fragments.at(i);
124 if ((this->*fragmentCallback)(queryData, fragment))
127 processedCharacters += fragment.length;
130 queryData->processedCharacters = processedCharacters;
136 bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, unsigned& startPosition, unsigned& endPosition) const
138 // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text fragment.
139 ASSERT(startPosition >= queryData->processedCharacters);
140 ASSERT(endPosition >= queryData->processedCharacters);
141 startPosition -= queryData->processedCharacters;
142 endPosition -= queryData->processedCharacters;
144 if (startPosition >= endPosition)
147 modifyStartEndPositionsRespectingLigatures(queryData, startPosition, endPosition);
148 if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition))
151 ASSERT_WITH_SECURITY_IMPLICATION(startPosition < endPosition);
155 void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, unsigned& startPosition, unsigned& endPosition) const
157 SVGTextLayoutAttributes* layoutAttributes = queryData->textRenderer->layoutAttributes();
158 Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes->textMetricsValues();
159 unsigned boxStart = queryData->textBox->start();
160 unsigned boxLength = queryData->textBox->len();
162 unsigned textMetricsOffset = 0;
163 unsigned textMetricsSize = textMetricsValues.size();
165 unsigned positionOffset = 0;
166 unsigned positionSize = layoutAttributes->context().textLength();
168 bool alterStartPosition = true;
169 bool alterEndPosition = true;
171 int lastPositionOffset = -1;
172 for (; textMetricsOffset < textMetricsSize && positionOffset < positionSize; ++textMetricsOffset) {
173 SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset];
175 // Advance to text box start location.
176 if (positionOffset < boxStart) {
177 positionOffset += metrics.length();
181 // Stop if we've finished processing this text box.
182 if (positionOffset >= boxStart + boxLength)
185 // If the start position maps to a character in the metrics list, we don't need to modify it.
186 if (startPosition == positionOffset)
187 alterStartPosition = false;
189 // If the start position maps to a character in the metrics list, we don't need to modify it.
190 if (endPosition == positionOffset)
191 alterEndPosition = false;
194 if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
195 if (alterStartPosition && startPosition > static_cast<unsigned>(lastPositionOffset) && startPosition < positionOffset) {
196 startPosition = lastPositionOffset;
197 alterStartPosition = false;
200 if (alterEndPosition && endPosition > static_cast<unsigned>(lastPositionOffset) && endPosition < positionOffset) {
201 endPosition = positionOffset;
202 alterEndPosition = false;
206 if (!alterStartPosition && !alterEndPosition)
209 lastPositionOffset = positionOffset;
210 positionOffset += metrics.length();
213 if (!alterStartPosition && !alterEndPosition)
216 if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
217 if (alterStartPosition && startPosition > static_cast<unsigned>(lastPositionOffset) && startPosition < positionOffset) {
218 startPosition = lastPositionOffset;
219 alterStartPosition = false;
222 if (alterEndPosition && endPosition > static_cast<unsigned>(lastPositionOffset) && endPosition < positionOffset) {
223 endPosition = positionOffset;
224 alterEndPosition = false;
229 // numberOfCharacters() implementation
230 bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const
236 unsigned SVGTextQuery::numberOfCharacters() const
238 if (m_textBoxes.isEmpty())
242 executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback);
243 return data.processedCharacters;
246 // textLength() implementation
247 struct TextLengthData : SVGTextQuery::Data {
256 bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
258 TextLengthData* data = static_cast<TextLengthData*>(queryData);
259 data->textLength += queryData->isVerticalText ? fragment.height : fragment.width;
263 float SVGTextQuery::textLength() const
265 if (m_textBoxes.isEmpty())
269 executeQuery(&data, &SVGTextQuery::textLengthCallback);
270 return data.textLength;
273 // subStringLength() implementation
274 struct SubStringLengthData : SVGTextQuery::Data {
275 SubStringLengthData(unsigned queryStartPosition, unsigned queryLength)
276 : startPosition(queryStartPosition)
277 , length(queryLength)
282 unsigned startPosition;
285 float subStringLength;
288 bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
290 SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData);
292 unsigned startPosition = data->startPosition;
293 unsigned endPosition = startPosition + data->length;
294 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
297 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(*queryData->textRenderer, fragment.characterOffset + startPosition, endPosition - startPosition);
298 data->subStringLength += queryData->isVerticalText ? metrics.height() : metrics.width();
302 float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const
304 if (m_textBoxes.isEmpty())
307 SubStringLengthData data(startPosition, length);
308 executeQuery(&data, &SVGTextQuery::subStringLengthCallback);
309 return data.subStringLength;
312 // startPositionOfCharacter() implementation
313 struct StartPositionOfCharacterData : SVGTextQuery::Data {
314 StartPositionOfCharacterData(unsigned queryPosition)
315 : position(queryPosition)
320 FloatPoint startPosition;
323 bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
325 StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData);
327 unsigned startPosition = data->position;
328 unsigned endPosition = startPosition + 1;
329 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
332 data->startPosition = FloatPoint(fragment.x, fragment.y);
335 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(*queryData->textRenderer, fragment.characterOffset, startPosition);
336 if (queryData->isVerticalText)
337 data->startPosition.move(0, metrics.height());
339 data->startPosition.move(metrics.width(), 0);
342 AffineTransform fragmentTransform;
343 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
344 if (fragmentTransform.isIdentity())
347 data->startPosition = fragmentTransform.mapPoint(data->startPosition);
351 SVGPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const
353 if (m_textBoxes.isEmpty())
356 StartPositionOfCharacterData data(position);
357 executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback);
358 return data.startPosition;
361 // endPositionOfCharacter() implementation
362 struct EndPositionOfCharacterData : SVGTextQuery::Data {
363 EndPositionOfCharacterData(unsigned queryPosition)
364 : position(queryPosition)
369 FloatPoint endPosition;
372 bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
374 EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData);
376 unsigned startPosition = data->position;
377 unsigned endPosition = startPosition + 1;
378 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
381 data->endPosition = FloatPoint(fragment.x, fragment.y);
383 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(*queryData->textRenderer, fragment.characterOffset, startPosition + 1);
384 if (queryData->isVerticalText)
385 data->endPosition.move(0, metrics.height());
387 data->endPosition.move(metrics.width(), 0);
389 AffineTransform fragmentTransform;
390 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
391 if (fragmentTransform.isIdentity())
394 data->endPosition = fragmentTransform.mapPoint(data->endPosition);
398 SVGPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const
400 if (m_textBoxes.isEmpty())
403 EndPositionOfCharacterData data(position);
404 executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback);
405 return data.endPosition;
408 // rotationOfCharacter() implementation
409 struct RotationOfCharacterData : SVGTextQuery::Data {
410 RotationOfCharacterData(unsigned queryPosition)
411 : position(queryPosition)
420 bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
422 RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData);
424 unsigned startPosition = data->position;
425 unsigned endPosition = startPosition + 1;
426 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
429 AffineTransform fragmentTransform;
430 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
431 if (fragmentTransform.isIdentity())
434 fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale());
435 data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a())));
441 float SVGTextQuery::rotationOfCharacter(unsigned position) const
443 if (m_textBoxes.isEmpty())
446 RotationOfCharacterData data(position);
447 executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback);
448 return data.rotation;
451 // extentOfCharacter() implementation
452 struct ExtentOfCharacterData : SVGTextQuery::Data {
453 ExtentOfCharacterData(unsigned queryPosition)
454 : position(queryPosition)
462 static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent)
464 float scalingFactor = queryData->textRenderer->scalingFactor();
465 ASSERT(scalingFactor);
467 extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->scaledFont().fontMetrics().floatAscent() / scalingFactor));
470 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(*queryData->textRenderer, fragment.characterOffset, startPosition);
471 if (queryData->isVerticalText)
472 extent.move(0, metrics.height());
474 extent.move(metrics.width(), 0);
477 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(*queryData->textRenderer, fragment.characterOffset + startPosition, 1);
478 extent.setSize(FloatSize(metrics.width(), metrics.height()));
480 AffineTransform fragmentTransform;
481 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
482 if (fragmentTransform.isIdentity())
485 extent = fragmentTransform.mapRect(extent);
488 bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
490 ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData);
492 unsigned startPosition = data->position;
493 unsigned endPosition = startPosition + 1;
494 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
497 calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent);
501 FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const
503 if (m_textBoxes.isEmpty())
506 ExtentOfCharacterData data(position);
507 executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback);
511 // characterNumberAtPosition() implementation
512 struct CharacterNumberAtPositionData : SVGTextQuery::Data {
513 CharacterNumberAtPositionData(const FloatPoint& queryPosition)
514 : position(queryPosition)
521 bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const
523 CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData);
526 for (unsigned i = 0; i < fragment.length; ++i) {
527 unsigned startPosition = data->processedCharacters + i;
528 unsigned endPosition = startPosition + 1;
529 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
532 calculateGlyphBoundaries(queryData, fragment, startPosition, extent);
533 if (extent.contains(data->position)) {
534 data->processedCharacters += i;
542 int SVGTextQuery::characterNumberAtPosition(const SVGPoint& position) const
544 if (m_textBoxes.isEmpty())
547 CharacterNumberAtPositionData data(position);
548 if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback))
551 return data.processedCharacters;