Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / core / layout / svg / SVGTextQuery.cpp
blob2a0ef770c6bf40fe76e7c888abf3ca9aa33dea6a
1 /*
2 * Copyright (C) Research In Motion Limited 2010-2012. All rights reserved.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
20 #include "config.h"
21 #include "core/layout/svg/SVGTextQuery.h"
23 #include "core/layout/LayoutBlockFlow.h"
24 #include "core/layout/LayoutInline.h"
25 #include "core/layout/line/InlineFlowBox.h"
26 #include "core/layout/svg/LayoutSVGInlineText.h"
27 #include "core/layout/svg/SVGTextFragment.h"
28 #include "core/layout/svg/SVGTextMetrics.h"
29 #include "core/layout/svg/line/SVGInlineTextBox.h"
30 #include "platform/FloatConversion.h"
31 #include "wtf/MathExtras.h"
32 #include "wtf/Vector.h"
34 namespace blink {
36 // Base structure for callback user data
37 struct QueryData {
38 QueryData()
39 : isVerticalText(false)
40 , currentOffset(0)
41 , textLayoutObject(nullptr)
42 , textBox(nullptr)
46 bool isVerticalText;
47 unsigned currentOffset;
48 LayoutSVGInlineText* textLayoutObject;
49 const SVGInlineTextBox* textBox;
52 static inline InlineFlowBox* flowBoxForLayoutObject(LayoutObject* layoutObject)
54 if (!layoutObject)
55 return nullptr;
57 if (layoutObject->isLayoutBlock()) {
58 // If we're given a block element, it has to be a LayoutSVGText.
59 ASSERT(layoutObject->isSVGText());
60 LayoutBlockFlow* layoutBlockFlow = toLayoutBlockFlow(layoutObject);
62 // LayoutSVGText only ever contains a single line box.
63 InlineFlowBox* flowBox = layoutBlockFlow->firstLineBox();
64 ASSERT(flowBox == layoutBlockFlow->lastLineBox());
65 return flowBox;
68 if (layoutObject->isLayoutInline()) {
69 // We're given a LayoutSVGInline or objects that derive from it (LayoutSVGTSpan / LayoutSVGTextPath)
70 LayoutInline* layoutInline = toLayoutInline(layoutObject);
72 // LayoutSVGInline only ever contains a single line box.
73 InlineFlowBox* flowBox = layoutInline->firstLineBox();
74 ASSERT(flowBox == layoutInline->lastLineBox());
75 return flowBox;
78 ASSERT_NOT_REACHED();
79 return nullptr;
82 static void collectTextBoxesInFlowBox(InlineFlowBox* flowBox, Vector<SVGInlineTextBox*>& textBoxes)
84 if (!flowBox)
85 return;
87 for (InlineBox* child = flowBox->firstChild(); child; child = child->nextOnLine()) {
88 if (child->isInlineFlowBox()) {
89 // Skip generated content.
90 if (!child->layoutObject().node())
91 continue;
93 collectTextBoxesInFlowBox(toInlineFlowBox(child), textBoxes);
94 continue;
97 if (child->isSVGInlineTextBox())
98 textBoxes.append(toSVGInlineTextBox(child));
102 typedef bool ProcessTextFragmentCallback(QueryData*, const SVGTextFragment&);
104 static bool queryTextBox(QueryData* queryData, const SVGInlineTextBox* textBox, ProcessTextFragmentCallback fragmentCallback)
106 queryData->textBox = textBox;
107 queryData->textLayoutObject = &toLayoutSVGInlineText(textBox->layoutObject());
109 queryData->isVerticalText = textBox->layoutObject().style()->svgStyle().isVerticalWritingMode();
111 // Loop over all text fragments in this text box, firing a callback for each.
112 for (const SVGTextFragment& fragment : textBox->textFragments()) {
113 if (fragmentCallback(queryData, fragment))
114 return true;
116 return false;
119 // Execute a query in "spatial" order starting at |queryRoot|. This means
120 // walking the lines boxes in the order they would get painted.
121 static void spatialQuery(LayoutObject* queryRoot, QueryData* queryData, ProcessTextFragmentCallback fragmentCallback)
123 Vector<SVGInlineTextBox*> textBoxes;
124 collectTextBoxesInFlowBox(flowBoxForLayoutObject(queryRoot), textBoxes);
126 // Loop over all text boxes
127 for (const SVGInlineTextBox* textBox : textBoxes) {
128 if (queryTextBox(queryData, textBox, fragmentCallback))
129 return;
133 static void collectTextBoxesInLogicalOrder(const LayoutSVGInlineText& textLayoutObject, Vector<SVGInlineTextBox*>& textBoxes)
135 textBoxes.shrink(0);
136 for (InlineTextBox* textBox = textLayoutObject.firstTextBox(); textBox; textBox = textBox->nextTextBox())
137 textBoxes.append(toSVGInlineTextBox(textBox));
138 std::sort(textBoxes.begin(), textBoxes.end(), InlineTextBox::compareByStart);
141 // Execute a query in "logical" order starting at |queryRoot|. This means
142 // walking the lines boxes for each layout object in layout tree (pre)order.
143 static void logicalQuery(LayoutObject* queryRoot, QueryData* queryData, ProcessTextFragmentCallback fragmentCallback)
145 if (!queryRoot)
146 return;
148 // Walk the layout tree in pre-order, starting at the specified root, and
149 // run the query for each text node.
150 Vector<SVGInlineTextBox*> textBoxes;
151 for (LayoutObject* layoutObject = queryRoot->slowFirstChild(); layoutObject; layoutObject = layoutObject->nextInPreOrder(queryRoot)) {
152 if (!layoutObject->isSVGInlineText())
153 continue;
155 LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(*layoutObject);
156 ASSERT(textLayoutObject.style());
158 // TODO(fs): Allow filtering the search earlier, since we should be
159 // able to trivially reject (prune) at least some of the queries.
160 collectTextBoxesInLogicalOrder(textLayoutObject, textBoxes);
162 for (const SVGInlineTextBox* textBox : textBoxes) {
163 if (queryTextBox(queryData, textBox, fragmentCallback))
164 return;
165 queryData->currentOffset += textBox->len();
170 static void modifyStartEndPositionsRespectingLigatures(const QueryData* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition)
172 const Vector<SVGTextMetrics>& textMetricsValues = queryData->textLayoutObject->layoutAttributes()->textMetricsValues();
174 unsigned textMetricsOffset = fragment.metricsListOffset;
175 int fragmentOffset = 0;
176 int fragmentEnd = static_cast<int>(fragment.length);
178 // Find the text metrics cell that start at or contain the character startPosition.
179 while (fragmentOffset < fragmentEnd) {
180 const SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset];
181 int glyphEnd = fragmentOffset + metrics.length();
182 if (startPosition < glyphEnd)
183 break;
184 fragmentOffset = glyphEnd;
185 textMetricsOffset++;
188 startPosition = fragmentOffset;
190 // Find the text metrics cell that contain or ends at the character endPosition.
191 while (fragmentOffset < fragmentEnd) {
192 const SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset];
193 fragmentOffset += metrics.length();
194 if (fragmentOffset >= endPosition)
195 break;
196 textMetricsOffset++;
199 endPosition = fragmentOffset;
202 static bool mapStartEndPositionsIntoFragmentCoordinates(const QueryData* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition)
204 unsigned boxStart = queryData->currentOffset;
206 // Make <startPosition, endPosition> offsets relative to the current text box.
207 startPosition -= boxStart;
208 endPosition -= boxStart;
210 // Reuse the same logic used for text selection & painting, to map our
211 // query start/length into start/endPositions of the current text fragment.
212 if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition))
213 return false;
215 modifyStartEndPositionsRespectingLigatures(queryData, fragment, startPosition, endPosition);
216 ASSERT(startPosition < endPosition);
217 return true;
220 // numberOfCharacters() implementation
221 static bool numberOfCharactersCallback(QueryData*, const SVGTextFragment&)
223 // no-op
224 return false;
227 unsigned SVGTextQuery::numberOfCharacters() const
229 QueryData data;
230 logicalQuery(m_queryRootLayoutObject, &data, numberOfCharactersCallback);
231 return data.currentOffset;
234 // textLength() implementation
235 struct TextLengthData : QueryData {
236 TextLengthData()
237 : textLength(0)
241 float textLength;
244 static bool textLengthCallback(QueryData* queryData, const SVGTextFragment& fragment)
246 TextLengthData* data = static_cast<TextLengthData*>(queryData);
247 data->textLength += queryData->isVerticalText ? fragment.height : fragment.width;
248 return false;
251 float SVGTextQuery::textLength() const
253 TextLengthData data;
254 logicalQuery(m_queryRootLayoutObject, &data, textLengthCallback);
255 return data.textLength;
258 // subStringLength() implementation
259 struct SubStringLengthData : QueryData {
260 SubStringLengthData(unsigned queryStartPosition, unsigned queryLength)
261 : startPosition(queryStartPosition)
262 , length(queryLength)
263 , subStringLength(0)
267 unsigned startPosition;
268 unsigned length;
270 float subStringLength;
273 static bool subStringLengthCallback(QueryData* queryData, const SVGTextFragment& fragment)
275 SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData);
277 int startPosition = data->startPosition;
278 int endPosition = startPosition + data->length;
279 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
280 return false;
282 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textLayoutObject, fragment.characterOffset + startPosition, endPosition - startPosition, queryData->textBox->direction());
283 data->subStringLength += queryData->isVerticalText ? metrics.height() : metrics.width();
284 return false;
287 float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const
289 SubStringLengthData data(startPosition, length);
290 logicalQuery(m_queryRootLayoutObject, &data, subStringLengthCallback);
291 return data.subStringLength;
294 // startPositionOfCharacter() implementation
295 struct StartPositionOfCharacterData : QueryData {
296 StartPositionOfCharacterData(unsigned queryPosition)
297 : position(queryPosition)
301 unsigned position;
302 FloatPoint startPosition;
305 static FloatPoint calculateGlyphPositionWithoutTransform(const QueryData* queryData, const SVGTextFragment& fragment, int offsetInFragment)
307 float glyphOffsetInDirection = 0;
308 if (offsetInFragment) {
309 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textLayoutObject, fragment.characterOffset, offsetInFragment, queryData->textBox->direction());
310 if (queryData->isVerticalText)
311 glyphOffsetInDirection = metrics.height();
312 else
313 glyphOffsetInDirection = metrics.width();
316 if (!queryData->textBox->isLeftToRightDirection()) {
317 float fragmentExtent = queryData->isVerticalText ? fragment.height : fragment.width;
318 glyphOffsetInDirection = fragmentExtent - glyphOffsetInDirection;
321 FloatPoint glyphPosition(fragment.x, fragment.y);
322 if (queryData->isVerticalText)
323 glyphPosition.move(0, glyphOffsetInDirection);
324 else
325 glyphPosition.move(glyphOffsetInDirection, 0);
327 return glyphPosition;
330 static FloatPoint calculateGlyphPosition(const QueryData* queryData, const SVGTextFragment& fragment, int offsetInFragment)
332 FloatPoint glyphPosition = calculateGlyphPositionWithoutTransform(queryData, fragment, offsetInFragment);
333 AffineTransform fragmentTransform;
334 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
335 if (!fragmentTransform.isIdentity())
336 glyphPosition = fragmentTransform.mapPoint(glyphPosition);
338 return glyphPosition;
341 static bool startPositionOfCharacterCallback(QueryData* queryData, const SVGTextFragment& fragment)
343 StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData);
345 int startPosition = data->position;
346 int endPosition = startPosition + 1;
347 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
348 return false;
350 data->startPosition = calculateGlyphPosition(queryData, fragment, startPosition);
351 return true;
354 FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const
356 StartPositionOfCharacterData data(position);
357 logicalQuery(m_queryRootLayoutObject, &data, startPositionOfCharacterCallback);
358 return data.startPosition;
361 // endPositionOfCharacter() implementation
362 struct EndPositionOfCharacterData : QueryData {
363 EndPositionOfCharacterData(unsigned queryPosition)
364 : position(queryPosition)
368 unsigned position;
369 FloatPoint endPosition;
372 static bool endPositionOfCharacterCallback(QueryData* queryData, const SVGTextFragment& fragment)
374 EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData);
376 int startPosition = data->position;
377 int endPosition = startPosition + 1;
378 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
379 return false;
381 // TODO(fs): mapStartEndPositionsIntoFragmentCoordinates(...) above applies
382 // some heuristics for ligatures, so why not just use endPosition here?
383 // (rather than startPosition+1)
384 data->endPosition = calculateGlyphPosition(queryData, fragment, startPosition + 1);
385 return true;
388 FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const
390 EndPositionOfCharacterData data(position);
391 logicalQuery(m_queryRootLayoutObject, &data, endPositionOfCharacterCallback);
392 return data.endPosition;
395 // rotationOfCharacter() implementation
396 struct RotationOfCharacterData : QueryData {
397 RotationOfCharacterData(unsigned queryPosition)
398 : position(queryPosition)
399 , rotation(0)
403 unsigned position;
404 float rotation;
407 static bool rotationOfCharacterCallback(QueryData* queryData, const SVGTextFragment& fragment)
409 RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData);
411 int startPosition = data->position;
412 int endPosition = startPosition + 1;
413 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
414 return false;
416 AffineTransform fragmentTransform;
417 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
418 if (fragmentTransform.isIdentity()) {
419 data->rotation = 0;
420 } else {
421 fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale());
422 data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a())));
425 return true;
428 float SVGTextQuery::rotationOfCharacter(unsigned position) const
430 RotationOfCharacterData data(position);
431 logicalQuery(m_queryRootLayoutObject, &data, rotationOfCharacterCallback);
432 return data.rotation;
435 // extentOfCharacter() implementation
436 struct ExtentOfCharacterData : QueryData {
437 ExtentOfCharacterData(unsigned queryPosition)
438 : position(queryPosition)
442 unsigned position;
443 FloatRect extent;
446 const SVGTextMetrics& findMetricsForCharacter(const Vector<SVGTextMetrics>& textMetricsValues, const SVGTextFragment& fragment, unsigned startInFragment)
448 // Find the text metrics cell that start at or contain the character at |startInFragment|.
449 unsigned textMetricsOffset = fragment.metricsListOffset;
450 unsigned fragmentOffset = 0;
451 while (fragmentOffset < fragment.length) {
452 const SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset++];
453 unsigned glyphEnd = fragmentOffset + metrics.length();
454 if (startInFragment < glyphEnd)
455 break;
456 fragmentOffset = glyphEnd;
458 return textMetricsValues[textMetricsOffset - 1];
461 static inline void calculateGlyphBoundaries(const QueryData* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent)
463 float scalingFactor = queryData->textLayoutObject->scalingFactor();
464 ASSERT(scalingFactor);
466 FloatPoint glyphPosition = calculateGlyphPositionWithoutTransform(queryData, fragment, startPosition);
467 glyphPosition.move(0, -queryData->textLayoutObject->scaledFont().fontMetrics().floatAscent() / scalingFactor);
468 extent.setLocation(glyphPosition);
470 // Use the SVGTextMetrics computed by SVGTextMetricsBuilder (which spends
471 // time attempting to compute more correct glyph bounds already, handling
472 // cursive scripts to some degree.)
473 const Vector<SVGTextMetrics>& textMetricsValues = queryData->textLayoutObject->layoutAttributes()->textMetricsValues();
474 const SVGTextMetrics& metrics = findMetricsForCharacter(textMetricsValues, fragment, startPosition);
476 // TODO(fs): Negative glyph extents seems kind of weird to have, but
477 // presently it can occur in some cases (like Arabic.)
478 FloatSize glyphSize(std::max<float>(metrics.width(), 0), std::max<float>(metrics.height(), 0));
479 extent.setSize(glyphSize);
481 // If RTL, adjust the starting point to align with the LHS of the glyph bounding box.
482 if (!queryData->textBox->isLeftToRightDirection()) {
483 if (queryData->isVerticalText)
484 extent.move(0, -glyphSize.height());
485 else
486 extent.move(-glyphSize.width(), 0);
489 AffineTransform fragmentTransform;
490 fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
492 extent = fragmentTransform.mapRect(extent);
495 static inline FloatRect calculateFragmentBoundaries(const LayoutSVGInlineText& textLayoutObject, const SVGTextFragment& fragment)
497 float scalingFactor = textLayoutObject.scalingFactor();
498 ASSERT(scalingFactor);
499 float baseline = textLayoutObject.scaledFont().fontMetrics().floatAscent() / scalingFactor;
501 AffineTransform fragmentTransform;
502 FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height);
503 fragment.buildFragmentTransform(fragmentTransform);
504 return fragmentTransform.mapRect(fragmentRect);
507 static bool extentOfCharacterCallback(QueryData* queryData, const SVGTextFragment& fragment)
509 ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData);
511 int startPosition = data->position;
512 int endPosition = startPosition + 1;
513 if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
514 return false;
516 calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent);
517 return true;
520 FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const
522 ExtentOfCharacterData data(position);
523 logicalQuery(m_queryRootLayoutObject, &data, extentOfCharacterCallback);
524 return data.extent;
527 // characterNumberAtPosition() implementation
528 struct CharacterNumberAtPositionData : QueryData {
529 CharacterNumberAtPositionData(const FloatPoint& queryPosition)
530 : position(queryPosition)
531 , hitLayoutObject(nullptr)
532 , offsetInTextNode(0)
536 int characterNumberWithin(const LayoutObject* queryRoot) const;
538 FloatPoint position;
539 LayoutObject* hitLayoutObject;
540 int offsetInTextNode;
543 int CharacterNumberAtPositionData::characterNumberWithin(const LayoutObject* queryRoot) const
545 // http://www.w3.org/TR/SVG/single-page.html#text-__svg__SVGTextContentElement__getCharNumAtPosition
546 // "If no such character exists, a value of -1 is returned."
547 if (!hitLayoutObject)
548 return -1;
549 ASSERT(queryRoot);
550 int characterNumber = offsetInTextNode;
552 // Accumulate the lengths of all the text nodes preceding the target layout
553 // object within the queried root, to get the complete character number.
554 for (const LayoutObject* layoutObject = hitLayoutObject->previousInPreOrder(queryRoot);
555 layoutObject; layoutObject = layoutObject->previousInPreOrder(queryRoot)) {
556 if (!layoutObject->isSVGInlineText())
557 continue;
558 characterNumber += toLayoutSVGInlineText(layoutObject)->resolvedTextLength();
560 return characterNumber;
563 static unsigned logicalOffsetInTextNode(const LayoutSVGInlineText& textLayoutObject, const SVGInlineTextBox* startTextBox, unsigned fragmentOffset)
565 Vector<SVGInlineTextBox*> textBoxes;
566 collectTextBoxesInLogicalOrder(textLayoutObject, textBoxes);
568 ASSERT(startTextBox);
569 size_t index = textBoxes.find(startTextBox);
570 ASSERT(index != kNotFound);
572 unsigned offset = fragmentOffset;
573 while (index) {
574 --index;
575 offset += textBoxes[index]->len();
577 return offset;
580 static bool characterNumberAtPositionCallback(QueryData* queryData, const SVGTextFragment& fragment)
582 CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData);
584 // Test the query point against the bounds of the entire fragment first.
585 FloatRect fragmentExtents = calculateFragmentBoundaries(*queryData->textLayoutObject, fragment);
586 if (!fragmentExtents.contains(data->position))
587 return false;
589 // Iterate through the glyphs in this fragment, and check if their extents
590 // contain the query point.
591 FloatRect extent;
592 const Vector<SVGTextMetrics>& textMetrics = queryData->textLayoutObject->layoutAttributes()->textMetricsValues();
593 unsigned textMetricsOffset = fragment.metricsListOffset;
594 unsigned fragmentOffset = 0;
595 while (fragmentOffset < fragment.length) {
596 calculateGlyphBoundaries(queryData, fragment, fragmentOffset, extent);
597 if (extent.contains(data->position)) {
598 // Compute the character offset of the glyph within the text node.
599 unsigned offsetInBox = fragment.characterOffset - queryData->textBox->start() + fragmentOffset;
600 data->offsetInTextNode = logicalOffsetInTextNode(*queryData->textLayoutObject, queryData->textBox, offsetInBox);
601 data->hitLayoutObject = data->textLayoutObject;
602 return true;
604 fragmentOffset += textMetrics[textMetricsOffset].length();
605 textMetricsOffset++;
607 return false;
610 int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const
612 CharacterNumberAtPositionData data(position);
613 spatialQuery(m_queryRootLayoutObject, &data, characterNumberAtPositionCallback);
614 return data.characterNumberWithin(m_queryRootLayoutObject);