--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/rendering/SVGTextQuery.cpp Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,467 @@
+/*
+ Copyright (C) Research In Motion Limited 2010. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+#include "SVGTextQuery.h"
+
+#if ENABLE(SVG)
+#include "FloatConversion.h"
+#include "InlineFlowBox.h"
+#include "RenderBlock.h"
+#include "RenderInline.h"
+#include "SVGInlineTextBox.h"
+#include "VisiblePosition.h"
+
+namespace WebCore {
+
+// Base structure for callback user data
+struct SVGTextQuery::Data {
+ Data()
+ : processedChunkCharacters(0)
+ {
+ }
+
+ unsigned processedChunkCharacters;
+};
+
+static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer)
+{
+ if (!renderer)
+ return 0;
+
+ if (renderer->isRenderBlock()) {
+ // If we're given a block element, it has to be a RenderSVGText.
+ ASSERT(renderer->isSVGText());
+ RenderBlock* renderBlock = toRenderBlock(renderer);
+
+ // RenderSVGText only ever contains a single line box.
+ InlineFlowBox* flowBox = renderBlock->firstLineBox();
+ ASSERT(flowBox == renderBlock->lastLineBox());
+ return flowBox;
+ }
+
+ if (renderer->isRenderInline()) {
+ // We're given a RenderSVGInline or objects that derive from it (RenderSVGTSpan / RenderSVGTextPath)
+ RenderInline* renderInline = toRenderInline(renderer);
+
+ // RenderSVGInline only ever contains a single line box.
+ InlineFlowBox* flowBox = renderInline->firstLineBox();
+ ASSERT(flowBox == renderInline->lastLineBox());
+ return flowBox;
+ }
+
+ ASSERT_NOT_REACHED();
+ return 0;
+}
+
+static inline float mapLengthThroughChunkTransformation(const SVGInlineTextBox* textBox, bool isVerticalText, float length)
+{
+ const AffineTransform& transform = textBox->chunkTransformation();
+ if (transform.isIdentity())
+ return length;
+
+ return narrowPrecisionToFloat(static_cast<double>(length) * (isVerticalText ? transform.d() : transform.a()));
+}
+
+SVGTextQuery::SVGTextQuery(RenderObject* renderer)
+{
+ collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
+}
+
+void SVGTextQuery::collectTextBoxesInFlowBox(InlineFlowBox* flowBox)
+{
+ if (!flowBox)
+ return;
+
+ for (InlineBox* child = flowBox->firstChild(); child; child = child->nextOnLine()) {
+ if (child->isInlineFlowBox()) {
+ // Skip generated content.
+ if (!child->renderer()->node())
+ continue;
+
+ collectTextBoxesInFlowBox(static_cast<InlineFlowBox*>(child));
+ continue;
+ }
+
+ ASSERT(child->isSVGInlineTextBox());
+ m_textBoxes.append(static_cast<SVGInlineTextBox*>(child));
+ }
+}
+
+bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextChunkPartCallback chunkPartCallback) const
+{
+ ASSERT(!m_textBoxes.isEmpty());
+ bool finished = false;
+
+ // Loop over all text boxes
+ const Vector<SVGInlineTextBox*>::const_iterator end = m_textBoxes.end();
+ for (Vector<SVGInlineTextBox*>::const_iterator it = m_textBoxes.begin(); it != end; ++it) {
+ const SVGInlineTextBox* textBox = *it;
+ const Vector<SVGTextChunkPart>& parts = textBox->svgTextChunkParts();
+
+ int processedCharacters = 0;
+
+ // Loop over all text chunk parts in this text box, firing a callback for each chunk part.
+ const Vector<SVGTextChunkPart>::const_iterator partEnd = parts.end();
+ for (Vector<SVGTextChunkPart>::const_iterator partIt = parts.begin(); partIt != partEnd; ++partIt) {
+ if ((this->*chunkPartCallback)(queryData, textBox, *partIt)) {
+ finished = true;
+ break;
+ }
+
+ processedCharacters += partIt->length;
+ }
+
+ if (finished)
+ break;
+
+ queryData->processedChunkCharacters += processedCharacters;
+ }
+
+ return finished;
+}
+
+bool SVGTextQuery::mapStartAndLengthIntoChunkPartCoordinates(Data* queryData, const SVGInlineTextBox* textBox, const SVGTextChunkPart& part, int& startPosition, int& endPosition) const
+{
+ // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text chunk part.
+ startPosition -= queryData->processedChunkCharacters;
+ endPosition -= queryData->processedChunkCharacters;
+ textBox->mapStartEndPositionsIntoChunkPartCoordinates(startPosition, endPosition, part);
+
+ // If startPosition < endPosition, then the position we're supposed to measure lies in this chunk part.
+ return startPosition < endPosition;
+}
+
+float SVGTextQuery::measureCharacterRange(const SVGInlineTextBox* textBox, RenderStyle* style, bool isVerticalText, int startPosition, int length) const
+{
+ // FIXME: Vertical writing mode needs to be handled more accurate.
+ if (isVerticalText)
+ return length * style->font().height();
+
+ const UChar* startCharacter = textBox->textRenderer()->characters() + textBox->start() + startPosition;
+ return style->font().floatWidth(svgTextRunForInlineTextBox(startCharacter, length, style, textBox));
+}
+
+// numberOfCharacters() implementation
+struct NumberOfCharactersData : SVGTextQuery::Data {
+ NumberOfCharactersData()
+ : characters(0)
+ {
+ }
+
+ unsigned characters;
+};
+
+bool SVGTextQuery::numberOfCharactersCallback(Data* queryData, const SVGInlineTextBox*, const SVGTextChunkPart& part) const
+{
+ NumberOfCharactersData* data = static_cast<NumberOfCharactersData*>(queryData);
+ data->characters += part.length;
+ return false;
+}
+
+unsigned SVGTextQuery::numberOfCharacters() const
+{
+ if (m_textBoxes.isEmpty())
+ return 0;
+
+ NumberOfCharactersData data;
+ executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback);
+ return data.characters;
+}
+
+// textLength() implementation
+struct TextLengthData : SVGTextQuery::Data {
+ TextLengthData()
+ : textLength(0.0f)
+ {
+ }
+
+ float textLength;
+};
+
+bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGInlineTextBox* textBox, const SVGTextChunkPart& part) const
+{
+ TextLengthData* data = static_cast<TextLengthData*>(queryData);
+
+ RenderStyle* style = textBox->textRenderer()->style();
+ ASSERT(style);
+
+ bool isVerticalText = isVerticalWritingMode(style->svgStyle());
+ float partLength = isVerticalText ? part.height : part.width;
+
+ data->textLength += mapLengthThroughChunkTransformation(textBox, isVerticalText, partLength);
+ return false;
+}
+
+float SVGTextQuery::textLength() const
+{
+ if (m_textBoxes.isEmpty())
+ return 0.0f;
+
+ TextLengthData data;
+ executeQuery(&data, &SVGTextQuery::textLengthCallback);
+ return data.textLength;
+}
+
+// subStringLength() implementation
+struct SubStringLengthData : SVGTextQuery::Data {
+ SubStringLengthData(unsigned queryStartPosition, unsigned queryLength)
+ : startPosition(queryStartPosition)
+ , length(queryLength)
+ , subStringLength(0.0f)
+ {
+ }
+
+ unsigned startPosition;
+ unsigned length;
+
+ float subStringLength;
+};
+
+bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGInlineTextBox* textBox, const SVGTextChunkPart& part) const
+{
+ SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData);
+
+ int startPosition = data->startPosition;
+ int endPosition = startPosition + data->length;
+ if (!mapStartAndLengthIntoChunkPartCoordinates(queryData, textBox, part, startPosition, endPosition))
+ return false;
+
+ RenderStyle* style = textBox->textRenderer()->style();
+ ASSERT(style);
+
+ bool isVerticalText = isVerticalWritingMode(style->svgStyle());
+ float partLength = measureCharacterRange(textBox, style, isVerticalWritingMode(style->svgStyle()), part.offset + startPosition, endPosition - startPosition);
+
+ data->subStringLength += mapLengthThroughChunkTransformation(textBox, isVerticalText, partLength);
+ return false;
+}
+
+float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const
+{
+ if (m_textBoxes.isEmpty())
+ return 0.0f;
+
+ SubStringLengthData data(startPosition, length);
+ executeQuery(&data, &SVGTextQuery::subStringLengthCallback);
+ return data.subStringLength;
+}
+
+// startPositionOfCharacter() implementation
+struct StartPositionOfCharacterData : SVGTextQuery::Data {
+ StartPositionOfCharacterData(unsigned queryPosition)
+ : position(queryPosition)
+ {
+ }
+
+ unsigned position;
+ FloatPoint startPosition;
+};
+
+bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGInlineTextBox* textBox, const SVGTextChunkPart& part) const
+{
+ StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData);
+
+ int startPosition = data->position;
+ int endPosition = startPosition + 1;
+ if (!mapStartAndLengthIntoChunkPartCoordinates(queryData, textBox, part, startPosition, endPosition))
+ return false;
+
+ const SVGChar& character = *(part.firstCharacter + startPosition);
+ data->startPosition = textBox->chunkTransformation().mapPoint(FloatPoint(character.x, character.y));
+ return true;
+}
+
+FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const
+{
+ if (m_textBoxes.isEmpty())
+ return FloatPoint();
+
+ StartPositionOfCharacterData data(position);
+ executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback);
+ return data.startPosition;
+}
+
+// endPositionOfCharacter() implementation
+struct EndPositionOfCharacterData : SVGTextQuery::Data {
+ EndPositionOfCharacterData(unsigned queryPosition)
+ : position(queryPosition)
+ {
+ }
+
+ unsigned position;
+ FloatPoint endPosition;
+};
+
+bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGInlineTextBox* textBox, const SVGTextChunkPart& part) const
+{
+ EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData);
+
+ int startPosition = data->position;
+ int endPosition = startPosition + 1;
+ if (!mapStartAndLengthIntoChunkPartCoordinates(queryData, textBox, part, startPosition, endPosition))
+ return false;
+
+ const SVGChar& character = *(part.firstCharacter + startPosition);
+ data->endPosition = FloatPoint(character.x, character.y);
+
+ RenderStyle* style = textBox->textRenderer()->style();
+ ASSERT(style);
+
+ bool isVerticalText = isVerticalWritingMode(style->svgStyle());
+ float glyphAdvance = measureCharacterRange(textBox, style, isVerticalText, part.offset + startPosition, 1);
+
+ if (isVerticalText)
+ data->endPosition.move(0.0f, glyphAdvance);
+ else
+ data->endPosition.move(glyphAdvance, 0.0f);
+
+ data->endPosition = textBox->chunkTransformation().mapPoint(data->endPosition);
+ return true;
+}
+
+FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const
+{
+ if (m_textBoxes.isEmpty())
+ return FloatPoint();
+
+ EndPositionOfCharacterData data(position);
+ executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback);
+ return data.endPosition;
+}
+
+// rotationOfCharacter() implementation
+struct RotationOfCharacterData : SVGTextQuery::Data {
+ RotationOfCharacterData(unsigned queryPosition)
+ : position(queryPosition)
+ , rotation(0.0f)
+ {
+ }
+
+ unsigned position;
+ float rotation;
+};
+
+bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGInlineTextBox* textBox, const SVGTextChunkPart& part) const
+{
+ RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData);
+
+ int startPosition = data->position;
+ int endPosition = startPosition + 1;
+ if (!mapStartAndLengthIntoChunkPartCoordinates(queryData, textBox, part, startPosition, endPosition))
+ return false;
+
+ const SVGChar& character = *(part.firstCharacter + startPosition);
+ data->rotation = character.angle;
+ return true;
+}
+
+float SVGTextQuery::rotationOfCharacter(unsigned position) const
+{
+ if (m_textBoxes.isEmpty())
+ return 0.0f;
+
+ RotationOfCharacterData data(position);
+ executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback);
+ return data.rotation;
+}
+
+// extentOfCharacter() implementation
+struct ExtentOfCharacterData : SVGTextQuery::Data {
+ ExtentOfCharacterData(unsigned queryPosition)
+ : position(queryPosition)
+ {
+ }
+
+ unsigned position;
+ FloatRect extent;
+};
+
+bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGInlineTextBox* textBox, const SVGTextChunkPart& part) const
+{
+ ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData);
+
+ int startPosition = data->position;
+ int endPosition = startPosition + 1;
+ if (!mapStartAndLengthIntoChunkPartCoordinates(queryData, textBox, part, startPosition, endPosition))
+ return false;
+
+ RenderStyle* style = textBox->textRenderer()->style();
+ ASSERT(style);
+
+ const SVGChar& character = *(part.firstCharacter + startPosition);
+ data->extent = textBox->calculateGlyphBoundaries(style, part.offset + startPosition, character);
+ return true;
+}
+
+FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const
+{
+ if (m_textBoxes.isEmpty())
+ return FloatRect();
+
+ ExtentOfCharacterData data(position);
+ executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback);
+ return data.extent;
+}
+
+// characterNumberAtPosition() implementation
+struct CharacterNumberAtPositionData : SVGTextQuery::Data {
+ CharacterNumberAtPositionData(const FloatPoint& queryPosition)
+ : characterNumber(0)
+ , position(queryPosition)
+ {
+ }
+
+ unsigned characterNumber;
+ FloatPoint position;
+};
+
+bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGInlineTextBox* textBox, const SVGTextChunkPart& part) const
+{
+ CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData);
+
+ RenderStyle* style = textBox->textRenderer()->style();
+ ASSERT(style);
+
+ for (int i = 0; i < part.length; ++i) {
+ FloatRect extent(textBox->calculateGlyphBoundaries(style, part.offset + i, *(part.firstCharacter + i)));
+ if (extent.contains(data->position))
+ return true;
+
+ ++data->characterNumber;
+ }
+
+ return false;
+}
+
+int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const
+{
+ if (m_textBoxes.isEmpty())
+ return -1;
+
+ CharacterNumberAtPositionData data(position);
+ if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback))
+ return -1;
+
+ return data.characterNumber;
+}
+
+}
+
+#endif