| Index: Source/core/layout/svg/SVGTextQuery.cpp
|
| diff --git a/Source/core/layout/svg/SVGTextQuery.cpp b/Source/core/layout/svg/SVGTextQuery.cpp
|
| index 490237a0386c0c4defe10d7ee1c2c110497d6082..8b63773d742ea3ef6d0a9a56f102c56c207deefe 100644
|
| --- a/Source/core/layout/svg/SVGTextQuery.cpp
|
| +++ b/Source/core/layout/svg/SVGTextQuery.cpp
|
| @@ -37,14 +37,14 @@ namespace blink {
|
| struct QueryData {
|
| QueryData()
|
| : isVerticalText(false)
|
| - , processedCharacters(0)
|
| + , currentOffset(0)
|
| , textLayoutObject(0)
|
| , textBox(0)
|
| {
|
| }
|
|
|
| bool isVerticalText;
|
| - unsigned processedCharacters;
|
| + unsigned currentOffset;
|
| LayoutSVGInlineText* textLayoutObject;
|
| const SVGInlineTextBox* textBox;
|
| };
|
| @@ -101,36 +101,70 @@ static void collectTextBoxesInFlowBox(InlineFlowBox* flowBox, Vector<SVGInlineTe
|
|
|
| typedef bool ProcessTextFragmentCallback(QueryData*, const SVGTextFragment&);
|
|
|
| -static bool executeQuery(LayoutObject* queryRoot, QueryData* queryData, ProcessTextFragmentCallback fragmentCallback)
|
| +static bool queryTextBox(QueryData* queryData, const SVGInlineTextBox* textBox, ProcessTextFragmentCallback fragmentCallback)
|
| +{
|
| + queryData->textBox = textBox;
|
| + queryData->textLayoutObject = &toLayoutSVGInlineText(textBox->layoutObject());
|
| +
|
| + queryData->isVerticalText = textBox->layoutObject().style()->svgStyle().isVerticalWritingMode();
|
| +
|
| + // Loop over all text fragments in this text box, firing a callback for each.
|
| + for (const SVGTextFragment& fragment : textBox->textFragments()) {
|
| + if (fragmentCallback(queryData, fragment))
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +// Execute a query in "spatial" order starting at |queryRoot|. This means
|
| +// walking the lines boxes in the order they would get painted.
|
| +static void spatialQuery(LayoutObject* queryRoot, QueryData* queryData, ProcessTextFragmentCallback fragmentCallback)
|
| {
|
| Vector<SVGInlineTextBox*> textBoxes;
|
| collectTextBoxesInFlowBox(flowBoxForLayoutObject(queryRoot), textBoxes);
|
|
|
| - unsigned processedCharacters = 0;
|
| - unsigned textBoxCount = textBoxes.size();
|
| -
|
| // Loop over all text boxes
|
| - for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) {
|
| - queryData->textBox = textBoxes[textBoxPosition];
|
| - queryData->textLayoutObject = &toLayoutSVGInlineText(queryData->textBox->layoutObject());
|
| - ASSERT(queryData->textLayoutObject->style());
|
| -
|
| - queryData->isVerticalText = queryData->textLayoutObject->style()->svgStyle().isVerticalWritingMode();
|
| - const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments();
|
| -
|
| - // Loop over all text fragments in this text box, firing a callback for each.
|
| - unsigned fragmentCount = fragments.size();
|
| - for (unsigned i = 0; i < fragmentCount; ++i) {
|
| - const SVGTextFragment& fragment = fragments.at(i);
|
| - if (fragmentCallback(queryData, fragment))
|
| - return true;
|
| -
|
| - processedCharacters += fragment.length;
|
| - }
|
| + for (const SVGInlineTextBox* textBox : textBoxes) {
|
| + if (queryTextBox(queryData, textBox, fragmentCallback))
|
| + return;
|
| + }
|
| +}
|
| +
|
| +static void collectTextBoxesInLogicalOrder(const LayoutSVGInlineText& textLayoutObject, Vector<SVGInlineTextBox*>& textBoxes)
|
| +{
|
| + textBoxes.shrink(0);
|
| + for (InlineTextBox* textBox = textLayoutObject.firstTextBox(); textBox; textBox = textBox->nextTextBox())
|
| + textBoxes.append(toSVGInlineTextBox(textBox));
|
| + std::sort(textBoxes.begin(), textBoxes.end(), InlineTextBox::compareByStart);
|
| +}
|
| +
|
| +// Execute a query in "logical" order starting at |queryRoot|. This means
|
| +// walking the lines boxes for each layout object in layout tree (pre)order.
|
| +static void logicalQuery(LayoutObject* queryRoot, QueryData* queryData, ProcessTextFragmentCallback fragmentCallback)
|
| +{
|
| + if (!queryRoot)
|
| + return;
|
| +
|
| + // Walk the layout tree in pre-order, starting at the specified root, and
|
| + // run the query for each text node.
|
| + Vector<SVGInlineTextBox*> textBoxes;
|
| + for (LayoutObject* layoutObject = queryRoot->slowFirstChild(); layoutObject; layoutObject = layoutObject->nextInPreOrder(queryRoot)) {
|
| + if (!layoutObject->isSVGInlineText())
|
| + continue;
|
|
|
| - queryData->processedCharacters = processedCharacters;
|
| + LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(*layoutObject);
|
| + ASSERT(textLayoutObject.style());
|
| +
|
| + // TODO(fs): Allow filtering the search earlier, since we should be
|
| + // able to trivially reject (prune) at least some of the queries.
|
| + collectTextBoxesInLogicalOrder(textLayoutObject, textBoxes);
|
| +
|
| + for (const SVGInlineTextBox* textBox : textBoxes) {
|
| + if (queryTextBox(queryData, textBox, fragmentCallback))
|
| + return;
|
| + queryData->currentOffset += textBox->len();
|
| + }
|
| }
|
| - return false;
|
| }
|
|
|
| static void modifyStartEndPositionsRespectingLigatures(const QueryData* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition)
|
| @@ -167,9 +201,11 @@ static void modifyStartEndPositionsRespectingLigatures(const QueryData* queryDat
|
|
|
| static bool mapStartEndPositionsIntoFragmentCoordinates(const QueryData* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition)
|
| {
|
| + unsigned boxStart = queryData->currentOffset;
|
| +
|
| // Make <startPosition, endPosition> offsets relative to the current text box.
|
| - startPosition -= queryData->processedCharacters;
|
| - endPosition -= queryData->processedCharacters;
|
| + startPosition -= boxStart;
|
| + endPosition -= boxStart;
|
|
|
| // Reuse the same logic used for text selection & painting, to map our
|
| // query start/length into start/endPositions of the current text fragment.
|
| @@ -191,8 +227,8 @@ static bool numberOfCharactersCallback(QueryData*, const SVGTextFragment&)
|
| unsigned SVGTextQuery::numberOfCharacters() const
|
| {
|
| QueryData data;
|
| - executeQuery(m_queryRootLayoutObject, &data, numberOfCharactersCallback);
|
| - return data.processedCharacters;
|
| + logicalQuery(m_queryRootLayoutObject, &data, numberOfCharactersCallback);
|
| + return data.currentOffset;
|
| }
|
|
|
| // textLength() implementation
|
| @@ -215,7 +251,7 @@ static bool textLengthCallback(QueryData* queryData, const SVGTextFragment& frag
|
| float SVGTextQuery::textLength() const
|
| {
|
| TextLengthData data;
|
| - executeQuery(m_queryRootLayoutObject, &data, textLengthCallback);
|
| + logicalQuery(m_queryRootLayoutObject, &data, textLengthCallback);
|
| return data.textLength;
|
| }
|
|
|
| @@ -251,7 +287,7 @@ static bool subStringLengthCallback(QueryData* queryData, const SVGTextFragment&
|
| float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const
|
| {
|
| SubStringLengthData data(startPosition, length);
|
| - executeQuery(m_queryRootLayoutObject, &data, subStringLengthCallback);
|
| + logicalQuery(m_queryRootLayoutObject, &data, subStringLengthCallback);
|
| return data.subStringLength;
|
| }
|
|
|
| @@ -318,7 +354,7 @@ static bool startPositionOfCharacterCallback(QueryData* queryData, const SVGText
|
| FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const
|
| {
|
| StartPositionOfCharacterData data(position);
|
| - executeQuery(m_queryRootLayoutObject, &data, startPositionOfCharacterCallback);
|
| + logicalQuery(m_queryRootLayoutObject, &data, startPositionOfCharacterCallback);
|
| return data.startPosition;
|
| }
|
|
|
| @@ -352,7 +388,7 @@ static bool endPositionOfCharacterCallback(QueryData* queryData, const SVGTextFr
|
| FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const
|
| {
|
| EndPositionOfCharacterData data(position);
|
| - executeQuery(m_queryRootLayoutObject, &data, endPositionOfCharacterCallback);
|
| + logicalQuery(m_queryRootLayoutObject, &data, endPositionOfCharacterCallback);
|
| return data.endPosition;
|
| }
|
|
|
| @@ -392,7 +428,7 @@ static bool rotationOfCharacterCallback(QueryData* queryData, const SVGTextFragm
|
| float SVGTextQuery::rotationOfCharacter(unsigned position) const
|
| {
|
| RotationOfCharacterData data(position);
|
| - executeQuery(m_queryRootLayoutObject, &data, rotationOfCharacterCallback);
|
| + logicalQuery(m_queryRootLayoutObject, &data, rotationOfCharacterCallback);
|
| return data.rotation;
|
| }
|
|
|
| @@ -484,7 +520,7 @@ static bool extentOfCharacterCallback(QueryData* queryData, const SVGTextFragmen
|
| FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const
|
| {
|
| ExtentOfCharacterData data(position);
|
| - executeQuery(m_queryRootLayoutObject, &data, extentOfCharacterCallback);
|
| + logicalQuery(m_queryRootLayoutObject, &data, extentOfCharacterCallback);
|
| return data.extent;
|
| }
|
|
|
| @@ -492,12 +528,55 @@ FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const
|
| struct CharacterNumberAtPositionData : QueryData {
|
| CharacterNumberAtPositionData(const FloatPoint& queryPosition)
|
| : position(queryPosition)
|
| + , hitLayoutObject(nullptr)
|
| + , offsetInTextNode(0)
|
| {
|
| }
|
|
|
| + int characterNumberWithin(const LayoutObject* queryRoot) const;
|
| +
|
| FloatPoint position;
|
| + LayoutObject* hitLayoutObject;
|
| + int offsetInTextNode;
|
| };
|
|
|
| +int CharacterNumberAtPositionData::characterNumberWithin(const LayoutObject* queryRoot) const
|
| +{
|
| + // http://www.w3.org/TR/SVG/single-page.html#text-__svg__SVGTextContentElement__getCharNumAtPosition
|
| + // "If no such character exists, a value of -1 is returned."
|
| + if (!hitLayoutObject)
|
| + return -1;
|
| + ASSERT(queryRoot);
|
| + int characterNumber = offsetInTextNode;
|
| +
|
| + // Accumulate the lengths of all the text nodes preceding the target layout
|
| + // object within the queried root, to get the complete character number.
|
| + for (const LayoutObject* layoutObject = hitLayoutObject->previousInPreOrder(queryRoot);
|
| + layoutObject; layoutObject = layoutObject->previousInPreOrder(queryRoot)) {
|
| + if (!layoutObject->isSVGInlineText())
|
| + continue;
|
| + characterNumber += toLayoutSVGInlineText(layoutObject)->renderedTextLength();
|
| + }
|
| + return characterNumber;
|
| +}
|
| +
|
| +static unsigned logicalOffsetInTextNode(const LayoutSVGInlineText& textLayoutObject, const SVGInlineTextBox* startTextBox, unsigned fragmentOffset)
|
| +{
|
| + Vector<SVGInlineTextBox*> textBoxes;
|
| + collectTextBoxesInLogicalOrder(textLayoutObject, textBoxes);
|
| +
|
| + ASSERT(startTextBox);
|
| + size_t index = textBoxes.find(startTextBox);
|
| + ASSERT(index != kNotFound);
|
| +
|
| + unsigned offset = fragmentOffset;
|
| + while (index) {
|
| + --index;
|
| + offset += textBoxes[index]->len();
|
| + }
|
| + return offset;
|
| +}
|
| +
|
| static bool characterNumberAtPositionCallback(QueryData* queryData, const SVGTextFragment& fragment)
|
| {
|
| CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData);
|
| @@ -516,10 +595,10 @@ static bool characterNumberAtPositionCallback(QueryData* queryData, const SVGTex
|
| while (fragmentOffset < fragment.length) {
|
| calculateGlyphBoundaries(queryData, fragment, fragmentOffset, extent);
|
| if (extent.contains(data->position)) {
|
| - // Compute the character offset of the glyph within the text box
|
| - // and add to processedCharacters.
|
| - unsigned characterOffset = fragment.characterOffset + fragmentOffset;
|
| - data->processedCharacters += characterOffset - data->textBox->start();
|
| + // Compute the character offset of the glyph within the text node.
|
| + unsigned offsetInBox = fragment.characterOffset - queryData->textBox->start() + fragmentOffset;
|
| + data->offsetInTextNode = logicalOffsetInTextNode(*queryData->textLayoutObject, queryData->textBox, offsetInBox);
|
| + data->hitLayoutObject = data->textLayoutObject;
|
| return true;
|
| }
|
| fragmentOffset += textMetrics[textMetricsOffset].length();
|
| @@ -531,10 +610,8 @@ static bool characterNumberAtPositionCallback(QueryData* queryData, const SVGTex
|
| int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const
|
| {
|
| CharacterNumberAtPositionData data(position);
|
| - if (!executeQuery(m_queryRootLayoutObject, &data, characterNumberAtPositionCallback))
|
| - return -1;
|
| -
|
| - return data.processedCharacters;
|
| + spatialQuery(m_queryRootLayoutObject, &data, characterNumberAtPositionCallback);
|
| + return data.characterNumberWithin(m_queryRootLayoutObject);
|
| }
|
|
|
| }
|
|
|