WebCore/rendering/SVGTextQuery.cpp
changeset 0 4f2f89ce4247
--- /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