/* 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(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(child)); continue; } ASSERT(child->isSVGInlineTextBox()); m_textBoxes.append(static_cast(child)); } } bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextChunkPartCallback chunkPartCallback) const { ASSERT(!m_textBoxes.isEmpty()); bool finished = false; // Loop over all text boxes const Vector::const_iterator end = m_textBoxes.end(); for (Vector::const_iterator it = m_textBoxes.begin(); it != end; ++it) { const SVGInlineTextBox* textBox = *it; const Vector& 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::const_iterator partEnd = parts.end(); for (Vector::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(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(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(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(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(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(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(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(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