Chromium Code Reviews| 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..d5ec93898d4b7225304bc417bc5b55d1ce89125d 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 |
| +{ |
| + // https://svgwg.org/svg2-draft/single-page.html#text-__svg__SVGTextContentElement__getCharNumAtPosition |
|
pdr.
2015/04/16 21:13:25
Sorry for the churn. I use the svg2 draft for ever
fs
2015/04/17 11:05:40
Done.
|
| + // "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); |
| } |
| } |