blob: 35ca690e470858a80fa3da8deedabe7d8a7840d3 [file] [log] [blame]
/*
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