2 * Copyright (C) Research In Motion Limited 2010. 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"
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"
33 #include <wtf/MathExtras.h>
37 // Base structure for callback user data
38 struct SVGTextQuery::Data {
40 : isVerticalText(false)
41 , processedCharacters(0)
48 unsigned processedCharacters;
49 RenderSVGInlineText* textRenderer;
50 const SVGInlineTextBox* textBox;
53 static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer)
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);
63 // RenderSVGText only ever contains a single line box.
64 InlineFlowBox* flowBox = renderBlock->firstLineBox();
65 ASSERT(flowBox == renderBlock->lastLineBox());
69 if (renderer->isRenderInline()) {
70 // We're given a RenderSVGInline or objects that derive from it (RenderSVGTSpan / RenderSVGTextPath)
71 RenderInline* renderInline = toRenderInline(renderer);
73 // RenderSVGInline only ever contains a single line box.
74 InlineFlowBox* flowBox = renderInline->firstLineBox();
75 ASSERT(flowBox == renderInline->lastLineBox());
83 SVGTextQuery::SVGTextQuery(RenderObject* renderer)
85 collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
88 void SVGTextQuery::collectTextBoxesInFlowBox(InlineFlowBox* flowBox)
93 for (InlineBox* child = flowBox->firstChild(); child; child = child->nextOnLine()) {
94 if (child->isInlineFlowBox()) {
95 // Skip generated content.
96 if (!child->renderer()->node())
99 collectTextBoxesInFlowBox(static_cast<InlineFlowBox*>(child));
103 if (child->isSVGInlineTextBox())
104 m_textBoxes.append(static_cast<SVGInlineTextBox*>(child));
108 bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextFragmentCallback fragmentCallback) const
110 ASSERT(!m_textBoxes.isEmpty());
112 unsigned processedCharacters = 0;
113 unsigned textBoxCount = m_textBoxes.size();
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());
123 queryData->isVerticalText = queryData->textRenderer->style()->svgStyle()->isVerticalWritingMode();
124 const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments();
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))
133 processedCharacters += fragment.length;
136 queryData->processedCharacters = processedCharacters;
142 bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const
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;
148 if (startPosition >= endPosition || startPosition < 0 || endPosition < 0)
151 modifyStartEndPositionsRespectingLigatures(queryData, startPosition, endPosition);
152 if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition))
155 ASSERT(startPosition < endPosition);
159 void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, int& startPosition, int& endPosition) const
161 const SVGTextLayoutAttributes& layoutAttributes = queryData->textRenderer->layoutAttributes();
162 const Vector<float>& xValues = layoutAttributes.xValues();
163 const Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes.textMetricsValues();
165 unsigned boxStart = queryData->textBox->start();
166 unsigned boxLength = queryData->textBox->len();
168 unsigned textMetricsOffset = 0;
169 unsigned textMetricsSize = textMetricsValues.size();
171 unsigned positionOffset = 0;
172 unsigned positionSize = xValues.size();
174 bool alterStartPosition = true;
175 bool alterEndPosition = true;
177 int lastPositionOffset = -1;
178 for (; textMetricsOffset < textMetricsSize && positionOffset < positionSize; ++textMetricsOffset) {
179 const SVGTextMetrics& metrics = textMetricsValues.at(textMetricsOffset);
181 // Advance to text box start location.
182 if (positionOffset < boxStart) {
183 positionOffset += metrics.length();
187 // Stop if we've finished processing this text box.
188 if (positionOffset >= boxStart + boxLength)
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;
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;
200 if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
201 if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) {
202 startPosition = lastPositionOffset;
203 alterStartPosition = false;
206 if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) {
207 endPosition = positionOffset;
208 alterEndPosition = false;
212 if (!alterStartPosition && !alterEndPosition)
215 lastPositionOffset = positionOffset;
216 positionOffset += metrics.length();
219 if (!alterStartPosition && !alterEndPosition)
222 if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
223 if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) {
224 startPosition = lastPositionOffset;
225 alterStartPosition = false;
228 if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) {
229 endPosition = positionOffset;
230 alterEndPosition = false;
235 // numberOfCharacters() implementation
236 bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const
242 unsigned SVGTextQuery::numberOfCharacters() const
244 if (m_textBoxes.isEmpty())
248 executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback);
249 return data.processedCharacters;
252 // textLength() implementation
253 struct TextLengthData : SVGTextQuery::Data {
262 bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
264 TextLengthData* data = static_cast<TextLengthData*>(queryData);
265 data->textLength += queryData->isVerticalText ? fragment.height : fragment.width;
269 float SVGTextQuery::textLength() const
271 if (m_textBoxes.isEmpty())
275 executeQuery(&data, &SVGTextQuery::textLengthCallback);
276 return data.textLength;
279 // subStringLength() implementation
280 struct SubStringLengthData : SVGTextQuery::Data {
281 SubStringLengthData(unsigned queryStartPosition, unsigned queryLength)
282 : startPosition(queryStartPosition)
283 , length(queryLength)
288 unsigned startPosition;
291 float subStringLength;
294 bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
296 SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData);
298 int startPosition = data->startPosition;
299 int endPosition = startPosition + data->length;
300 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
303 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, endPosition - startPosition);
304 data->subStringLength += queryData->isVerticalText ? metrics.height() : metrics.width();
308 float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const
310 if (m_textBoxes.isEmpty())
313 SubStringLengthData data(startPosition, length);
314 executeQuery(&data, &SVGTextQuery::subStringLengthCallback);
315 return data.subStringLength;
318 // startPositionOfCharacter() implementation
319 struct StartPositionOfCharacterData : SVGTextQuery::Data {
320 StartPositionOfCharacterData(unsigned queryPosition)
321 : position(queryPosition)
326 FloatPoint startPosition;
329 bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
331 StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData);
333 int startPosition = data->position;
334 int endPosition = startPosition + 1;
335 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
338 data->startPosition = FloatPoint(fragment.x, fragment.y);
341 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition);
342 if (queryData->isVerticalText)
343 data->startPosition.move(0, metrics.height());
345 data->startPosition.move(metrics.width(), 0);
348 AffineTransform fragmentTransform;
349 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
350 if (fragmentTransform.isIdentity())
353 data->startPosition = fragmentTransform.mapPoint(data->startPosition);
357 FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const
359 if (m_textBoxes.isEmpty())
362 StartPositionOfCharacterData data(position);
363 executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback);
364 return data.startPosition;
367 // endPositionOfCharacter() implementation
368 struct EndPositionOfCharacterData : SVGTextQuery::Data {
369 EndPositionOfCharacterData(unsigned queryPosition)
370 : position(queryPosition)
375 FloatPoint endPosition;
378 bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
380 EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData);
382 int startPosition = data->position;
383 int endPosition = startPosition + 1;
384 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
387 data->endPosition = FloatPoint(fragment.x, fragment.y);
389 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition + 1);
390 if (queryData->isVerticalText)
391 data->endPosition.move(0, metrics.height());
393 data->endPosition.move(metrics.width(), 0);
395 AffineTransform fragmentTransform;
396 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
397 if (fragmentTransform.isIdentity())
400 data->endPosition = fragmentTransform.mapPoint(data->endPosition);
404 FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const
406 if (m_textBoxes.isEmpty())
409 EndPositionOfCharacterData data(position);
410 executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback);
411 return data.endPosition;
414 // rotationOfCharacter() implementation
415 struct RotationOfCharacterData : SVGTextQuery::Data {
416 RotationOfCharacterData(unsigned queryPosition)
417 : position(queryPosition)
426 bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
428 RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData);
430 int startPosition = data->position;
431 int endPosition = startPosition + 1;
432 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
435 AffineTransform fragmentTransform;
436 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
437 if (fragmentTransform.isIdentity())
440 fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale());
441 data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a())));
447 float SVGTextQuery::rotationOfCharacter(unsigned position) const
449 if (m_textBoxes.isEmpty())
452 RotationOfCharacterData data(position);
453 executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback);
454 return data.rotation;
457 // extentOfCharacter() implementation
458 struct ExtentOfCharacterData : SVGTextQuery::Data {
459 ExtentOfCharacterData(unsigned queryPosition)
460 : position(queryPosition)
468 static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent)
470 float scalingFactor = queryData->textRenderer->scalingFactor();
471 ASSERT(scalingFactor);
473 extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->scaledFont().fontMetrics().floatAscent() / scalingFactor));
476 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition);
477 if (queryData->isVerticalText)
478 extent.move(0, metrics.height());
480 extent.move(metrics.width(), 0);
483 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, 1);
484 extent.setSize(FloatSize(metrics.width(), metrics.height()));
486 AffineTransform fragmentTransform;
487 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
488 if (fragmentTransform.isIdentity())
491 extent = fragmentTransform.mapRect(extent);
494 bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
496 ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData);
498 int startPosition = data->position;
499 int endPosition = startPosition + 1;
500 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
503 calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent);
507 FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const
509 if (m_textBoxes.isEmpty())
512 ExtentOfCharacterData data(position);
513 executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback);
517 // characterNumberAtPosition() implementation
518 struct CharacterNumberAtPositionData : SVGTextQuery::Data {
519 CharacterNumberAtPositionData(const FloatPoint& queryPosition)
520 : position(queryPosition)
527 bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const
529 CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData);
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))
538 calculateGlyphBoundaries(queryData, fragment, startPosition, extent);
539 if (extent.contains(data->position)) {
540 data->processedCharacters += i;
548 int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const
550 if (m_textBoxes.isEmpty())
553 CharacterNumberAtPositionData data(position);
554 if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback))
557 return data.processedCharacters;