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.
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"
36 // Base structure for callback user data
39 : isVerticalText(false)
41 , textLayoutObject(nullptr)
47 unsigned currentOffset
;
48 LayoutSVGInlineText
* textLayoutObject
;
49 const SVGInlineTextBox
* textBox
;
52 static inline InlineFlowBox
* flowBoxForLayoutObject(LayoutObject
* layoutObject
)
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());
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());
82 static void collectTextBoxesInFlowBox(InlineFlowBox
* flowBox
, Vector
<SVGInlineTextBox
*>& textBoxes
)
87 for (InlineBox
* child
= flowBox
->firstChild(); child
; child
= child
->nextOnLine()) {
88 if (child
->isInlineFlowBox()) {
89 // Skip generated content.
90 if (!child
->layoutObject().node())
93 collectTextBoxesInFlowBox(toInlineFlowBox(child
), textBoxes
);
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
))
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
))
133 static void collectTextBoxesInLogicalOrder(const LayoutSVGInlineText
& textLayoutObject
, Vector
<SVGInlineTextBox
*>& textBoxes
)
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
)
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())
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
))
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
)
184 fragmentOffset
= glyphEnd
;
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
)
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
))
215 modifyStartEndPositionsRespectingLigatures(queryData
, fragment
, startPosition
, endPosition
);
216 ASSERT(startPosition
< endPosition
);
220 // numberOfCharacters() implementation
221 static bool numberOfCharactersCallback(QueryData
*, const SVGTextFragment
&)
227 unsigned SVGTextQuery::numberOfCharacters() const
230 logicalQuery(m_queryRootLayoutObject
, &data
, numberOfCharactersCallback
);
231 return data
.currentOffset
;
234 // textLength() implementation
235 struct TextLengthData
: QueryData
{
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
;
251 float SVGTextQuery::textLength() const
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
)
267 unsigned startPosition
;
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
))
282 SVGTextMetrics metrics
= SVGTextMetrics::measureCharacterRange(queryData
->textLayoutObject
, fragment
.characterOffset
+ startPosition
, endPosition
- startPosition
, queryData
->textBox
->direction());
283 data
->subStringLength
+= queryData
->isVerticalText
? metrics
.height() : metrics
.width();
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
)
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();
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
);
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
))
350 data
->startPosition
= calculateGlyphPosition(queryData
, fragment
, startPosition
);
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
)
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
))
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);
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
)
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
))
416 AffineTransform fragmentTransform
;
417 fragment
.buildFragmentTransform(fragmentTransform
, SVGTextFragment::TransformIgnoringTextLength
);
418 if (fragmentTransform
.isIdentity()) {
421 fragmentTransform
.scale(1 / fragmentTransform
.xScale(), 1 / fragmentTransform
.yScale());
422 data
->rotation
= narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform
.b(), fragmentTransform
.a())));
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
)
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
)
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());
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
))
516 calculateGlyphBoundaries(queryData
, fragment
, startPosition
, data
->extent
);
520 FloatRect
SVGTextQuery::extentOfCharacter(unsigned position
) const
522 ExtentOfCharacterData
data(position
);
523 logicalQuery(m_queryRootLayoutObject
, &data
, extentOfCharacterCallback
);
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;
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
)
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())
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
;
575 offset
+= textBoxes
[index
]->len();
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
))
589 // Iterate through the glyphs in this fragment, and check if their extents
590 // contain the query point.
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
;
604 fragmentOffset
+= textMetrics
[textMetricsOffset
].length();
610 int SVGTextQuery::characterNumberAtPosition(const FloatPoint
& position
) const
612 CharacterNumberAtPositionData
data(position
);
613 spatialQuery(m_queryRootLayoutObject
, &data
, characterNumberAtPositionCallback
);
614 return data
.characterNumberWithin(m_queryRootLayoutObject
);