1 /*
2  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
3  *
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.
8  *
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.
13  *
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.
18  */
19 
20 #include "config.h"
21 #include "SVGTextQuery.h"
22 
23 #if ENABLE(SVG)
24 #include "FloatConversion.h"
25 #include "InlineFlowBox.h"
26 #include "RenderBlock.h"
27 #include "RenderInline.h"
28 #include "RenderSVGInlineText.h"
29 #include "SVGInlineTextBox.h"
30 #include "SVGTextMetrics.h"
31 #include "VisiblePosition.h"
32 
33 #include <wtf/MathExtras.h>
34 
35 namespace WebCore {
36 
37 // Base structure for callback user data
38 struct SVGTextQuery::Data {
DataWebCore::SVGTextQuery::Data39     Data()
40         : isVerticalText(false)
41         , processedCharacters(0)
42         , textRenderer(0)
43         , textBox(0)
44     {
45     }
46 
47     bool isVerticalText;
48     unsigned processedCharacters;
49     RenderSVGInlineText* textRenderer;
50     const SVGInlineTextBox* textBox;
51 };
52 
flowBoxForRenderer(RenderObject * renderer)53 static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer)
54 {
55     if (!renderer)
56         return 0;
57 
58     if (renderer->isRenderBlock()) {
59         // If we're given a block element, it has to be a RenderSVGText.
60         ASSERT(renderer->isSVGText());
61         RenderBlock* renderBlock = toRenderBlock(renderer);
62 
63         // RenderSVGText only ever contains a single line box.
64         InlineFlowBox* flowBox = renderBlock->firstLineBox();
65         ASSERT(flowBox == renderBlock->lastLineBox());
66         return flowBox;
67     }
68 
69     if (renderer->isRenderInline()) {
70         // We're given a RenderSVGInline or objects that derive from it (RenderSVGTSpan / RenderSVGTextPath)
71         RenderInline* renderInline = toRenderInline(renderer);
72 
73         // RenderSVGInline only ever contains a single line box.
74         InlineFlowBox* flowBox = renderInline->firstLineBox();
75         ASSERT(flowBox == renderInline->lastLineBox());
76         return flowBox;
77     }
78 
79     ASSERT_NOT_REACHED();
80     return 0;
81 }
82 
SVGTextQuery(RenderObject * renderer)83 SVGTextQuery::SVGTextQuery(RenderObject* renderer)
84 {
85     collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
86 }
87 
collectTextBoxesInFlowBox(InlineFlowBox * flowBox)88 void SVGTextQuery::collectTextBoxesInFlowBox(InlineFlowBox* flowBox)
89 {
90     if (!flowBox)
91         return;
92 
93     for (InlineBox* child = flowBox->firstChild(); child; child = child->nextOnLine()) {
94         if (child->isInlineFlowBox()) {
95             // Skip generated content.
96             if (!child->renderer()->node())
97                 continue;
98 
99             collectTextBoxesInFlowBox(static_cast<InlineFlowBox*>(child));
100             continue;
101         }
102 
103         if (child->isSVGInlineTextBox())
104             m_textBoxes.append(static_cast<SVGInlineTextBox*>(child));
105     }
106 }
107 
executeQuery(Data * queryData,ProcessTextFragmentCallback fragmentCallback) const108 bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextFragmentCallback fragmentCallback) const
109 {
110     ASSERT(!m_textBoxes.isEmpty());
111 
112     unsigned processedCharacters = 0;
113     unsigned textBoxCount = m_textBoxes.size();
114 
115     // Loop over all text boxes
116     for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) {
117         queryData->textBox = m_textBoxes.at(textBoxPosition);
118         queryData->textRenderer = toRenderSVGInlineText(queryData->textBox->textRenderer());
119         ASSERT(queryData->textRenderer);
120         ASSERT(queryData->textRenderer->style());
121         ASSERT(queryData->textRenderer->style()->svgStyle());
122 
123         queryData->isVerticalText = queryData->textRenderer->style()->svgStyle()->isVerticalWritingMode();
124         const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments();
125 
126         // Loop over all text fragments in this text box, firing a callback for each.
127         unsigned fragmentCount = fragments.size();
128         for (unsigned i = 0; i < fragmentCount; ++i) {
129             const SVGTextFragment& fragment = fragments.at(i);
130             if ((this->*fragmentCallback)(queryData, fragment))
131                 return true;
132 
133             processedCharacters += fragment.length;
134         }
135 
136         queryData->processedCharacters = processedCharacters;
137     }
138 
139     return false;
140 }
141 
mapStartEndPositionsIntoFragmentCoordinates(Data * queryData,const SVGTextFragment & fragment,int & startPosition,int & endPosition) const142 bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const
143 {
144     // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text fragment.
145     startPosition -= queryData->processedCharacters;
146     endPosition -= queryData->processedCharacters;
147 
148     if (startPosition >= endPosition || startPosition < 0 || endPosition < 0)
149         return false;
150 
151     modifyStartEndPositionsRespectingLigatures(queryData, startPosition, endPosition);
152     if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition))
153         return false;
154 
155     ASSERT(startPosition < endPosition);
156     return true;
157 }
158 
modifyStartEndPositionsRespectingLigatures(Data * queryData,int & startPosition,int & endPosition) const159 void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, int& startPosition, int& endPosition) const
160 {
161     const SVGTextLayoutAttributes& layoutAttributes = queryData->textRenderer->layoutAttributes();
162     const Vector<float>& xValues = layoutAttributes.xValues();
163     const Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes.textMetricsValues();
164 
165     unsigned boxStart = queryData->textBox->start();
166     unsigned boxLength = queryData->textBox->len();
167 
168     unsigned textMetricsOffset = 0;
169     unsigned textMetricsSize = textMetricsValues.size();
170 
171     unsigned positionOffset = 0;
172     unsigned positionSize = xValues.size();
173 
174     bool alterStartPosition = true;
175     bool alterEndPosition = true;
176 
177     int lastPositionOffset = -1;
178     for (; textMetricsOffset < textMetricsSize && positionOffset < positionSize; ++textMetricsOffset) {
179         const SVGTextMetrics& metrics = textMetricsValues.at(textMetricsOffset);
180 
181         // Advance to text box start location.
182         if (positionOffset < boxStart) {
183             positionOffset += metrics.length();
184             continue;
185         }
186 
187         // Stop if we've finished processing this text box.
188         if (positionOffset >= boxStart + boxLength)
189             break;
190 
191         // If the start position maps to a character in the metrics list, we don't need to modify it.
192         if (startPosition == static_cast<int>(positionOffset))
193             alterStartPosition = false;
194 
195         // If the start position maps to a character in the metrics list, we don't need to modify it.
196         if (endPosition == static_cast<int>(positionOffset))
197             alterEndPosition = false;
198 
199         // Detect ligatures.
200         if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
201             if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) {
202                 startPosition = lastPositionOffset;
203                 alterStartPosition = false;
204             }
205 
206             if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) {
207                 endPosition = positionOffset;
208                 alterEndPosition = false;
209             }
210         }
211 
212         if (!alterStartPosition && !alterEndPosition)
213             break;
214 
215         lastPositionOffset = positionOffset;
216         positionOffset += metrics.length();
217     }
218 
219     if (!alterStartPosition && !alterEndPosition)
220         return;
221 
222     if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
223         if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) {
224             startPosition = lastPositionOffset;
225             alterStartPosition = false;
226         }
227 
228         if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) {
229             endPosition = positionOffset;
230             alterEndPosition = false;
231         }
232     }
233 }
234 
235 // numberOfCharacters() implementation
numberOfCharactersCallback(Data *,const SVGTextFragment &) const236 bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const
237 {
238     // no-op
239     return false;
240 }
241 
numberOfCharacters() const242 unsigned SVGTextQuery::numberOfCharacters() const
243 {
244     if (m_textBoxes.isEmpty())
245         return 0;
246 
247     Data data;
248     executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback);
249     return data.processedCharacters;
250 }
251 
252 // textLength() implementation
253 struct TextLengthData : SVGTextQuery::Data {
TextLengthDataWebCore::TextLengthData254     TextLengthData()
255         : textLength(0)
256     {
257     }
258 
259     float textLength;
260 };
261 
textLengthCallback(Data * queryData,const SVGTextFragment & fragment) const262 bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
263 {
264     TextLengthData* data = static_cast<TextLengthData*>(queryData);
265     data->textLength += queryData->isVerticalText ? fragment.height : fragment.width;
266     return false;
267 }
268 
textLength() const269 float SVGTextQuery::textLength() const
270 {
271     if (m_textBoxes.isEmpty())
272         return 0;
273 
274     TextLengthData data;
275     executeQuery(&data, &SVGTextQuery::textLengthCallback);
276     return data.textLength;
277 }
278 
279 // subStringLength() implementation
280 struct SubStringLengthData : SVGTextQuery::Data {
SubStringLengthDataWebCore::SubStringLengthData281     SubStringLengthData(unsigned queryStartPosition, unsigned queryLength)
282         : startPosition(queryStartPosition)
283         , length(queryLength)
284         , subStringLength(0)
285     {
286     }
287 
288     unsigned startPosition;
289     unsigned length;
290 
291     float subStringLength;
292 };
293 
subStringLengthCallback(Data * queryData,const SVGTextFragment & fragment) const294 bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
295 {
296     SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData);
297 
298     int startPosition = data->startPosition;
299     int endPosition = startPosition + data->length;
300     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
301         return false;
302 
303     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, endPosition - startPosition);
304     data->subStringLength += queryData->isVerticalText ? metrics.height() : metrics.width();
305     return false;
306 }
307 
subStringLength(unsigned startPosition,unsigned length) const308 float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const
309 {
310     if (m_textBoxes.isEmpty())
311         return 0;
312 
313     SubStringLengthData data(startPosition, length);
314     executeQuery(&data, &SVGTextQuery::subStringLengthCallback);
315     return data.subStringLength;
316 }
317 
318 // startPositionOfCharacter() implementation
319 struct StartPositionOfCharacterData : SVGTextQuery::Data {
StartPositionOfCharacterDataWebCore::StartPositionOfCharacterData320     StartPositionOfCharacterData(unsigned queryPosition)
321         : position(queryPosition)
322     {
323     }
324 
325     unsigned position;
326     FloatPoint startPosition;
327 };
328 
startPositionOfCharacterCallback(Data * queryData,const SVGTextFragment & fragment) const329 bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
330 {
331     StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData);
332 
333     int startPosition = data->position;
334     int endPosition = startPosition + 1;
335     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
336         return false;
337 
338     data->startPosition = FloatPoint(fragment.x, fragment.y);
339 
340     if (startPosition) {
341         SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition);
342         if (queryData->isVerticalText)
343             data->startPosition.move(0, metrics.height());
344         else
345             data->startPosition.move(metrics.width(), 0);
346     }
347 
348     AffineTransform fragmentTransform;
349     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
350     if (fragmentTransform.isIdentity())
351         return true;
352 
353     data->startPosition = fragmentTransform.mapPoint(data->startPosition);
354     return true;
355 }
356 
startPositionOfCharacter(unsigned position) const357 FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const
358 {
359     if (m_textBoxes.isEmpty())
360         return FloatPoint();
361 
362     StartPositionOfCharacterData data(position);
363     executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback);
364     return data.startPosition;
365 }
366 
367 // endPositionOfCharacter() implementation
368 struct EndPositionOfCharacterData : SVGTextQuery::Data {
EndPositionOfCharacterDataWebCore::EndPositionOfCharacterData369     EndPositionOfCharacterData(unsigned queryPosition)
370         : position(queryPosition)
371     {
372     }
373 
374     unsigned position;
375     FloatPoint endPosition;
376 };
377 
endPositionOfCharacterCallback(Data * queryData,const SVGTextFragment & fragment) const378 bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
379 {
380     EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData);
381 
382     int startPosition = data->position;
383     int endPosition = startPosition + 1;
384     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
385         return false;
386 
387     data->endPosition = FloatPoint(fragment.x, fragment.y);
388 
389     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition + 1);
390     if (queryData->isVerticalText)
391         data->endPosition.move(0, metrics.height());
392     else
393         data->endPosition.move(metrics.width(), 0);
394 
395     AffineTransform fragmentTransform;
396     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
397     if (fragmentTransform.isIdentity())
398         return true;
399 
400     data->endPosition = fragmentTransform.mapPoint(data->endPosition);
401     return true;
402 }
403 
endPositionOfCharacter(unsigned position) const404 FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const
405 {
406     if (m_textBoxes.isEmpty())
407         return FloatPoint();
408 
409     EndPositionOfCharacterData data(position);
410     executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback);
411     return data.endPosition;
412 }
413 
414 // rotationOfCharacter() implementation
415 struct RotationOfCharacterData : SVGTextQuery::Data {
RotationOfCharacterDataWebCore::RotationOfCharacterData416     RotationOfCharacterData(unsigned queryPosition)
417         : position(queryPosition)
418         , rotation(0)
419     {
420     }
421 
422     unsigned position;
423     float rotation;
424 };
425 
rotationOfCharacterCallback(Data * queryData,const SVGTextFragment & fragment) const426 bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
427 {
428     RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData);
429 
430     int startPosition = data->position;
431     int endPosition = startPosition + 1;
432     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
433         return false;
434 
435     AffineTransform fragmentTransform;
436     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
437     if (fragmentTransform.isIdentity())
438         data->rotation = 0;
439     else {
440         fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale());
441         data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a())));
442     }
443 
444     return true;
445 }
446 
rotationOfCharacter(unsigned position) const447 float SVGTextQuery::rotationOfCharacter(unsigned position) const
448 {
449     if (m_textBoxes.isEmpty())
450         return 0;
451 
452     RotationOfCharacterData data(position);
453     executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback);
454     return data.rotation;
455 }
456 
457 // extentOfCharacter() implementation
458 struct ExtentOfCharacterData : SVGTextQuery::Data {
ExtentOfCharacterDataWebCore::ExtentOfCharacterData459     ExtentOfCharacterData(unsigned queryPosition)
460         : position(queryPosition)
461     {
462     }
463 
464     unsigned position;
465     FloatRect extent;
466 };
467 
calculateGlyphBoundaries(SVGTextQuery::Data * queryData,const SVGTextFragment & fragment,int startPosition,FloatRect & extent)468 static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent)
469 {
470     float scalingFactor = queryData->textRenderer->scalingFactor();
471     ASSERT(scalingFactor);
472 
473     extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->scaledFont().fontMetrics().floatAscent() / scalingFactor));
474 
475     if (startPosition) {
476         SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition);
477         if (queryData->isVerticalText)
478             extent.move(0, metrics.height());
479         else
480             extent.move(metrics.width(), 0);
481     }
482 
483     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, 1);
484     extent.setSize(FloatSize(metrics.width(), metrics.height()));
485 
486     AffineTransform fragmentTransform;
487     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
488     if (fragmentTransform.isIdentity())
489         return;
490 
491     extent = fragmentTransform.mapRect(extent);
492 }
493 
extentOfCharacterCallback(Data * queryData,const SVGTextFragment & fragment) const494 bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
495 {
496     ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData);
497 
498     int startPosition = data->position;
499     int endPosition = startPosition + 1;
500     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
501         return false;
502 
503     calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent);
504     return true;
505 }
506 
extentOfCharacter(unsigned position) const507 FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const
508 {
509     if (m_textBoxes.isEmpty())
510         return FloatRect();
511 
512     ExtentOfCharacterData data(position);
513     executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback);
514     return data.extent;
515 }
516 
517 // characterNumberAtPosition() implementation
518 struct CharacterNumberAtPositionData : SVGTextQuery::Data {
CharacterNumberAtPositionDataWebCore::CharacterNumberAtPositionData519     CharacterNumberAtPositionData(const FloatPoint& queryPosition)
520         : position(queryPosition)
521     {
522     }
523 
524     FloatPoint position;
525 };
526 
characterNumberAtPositionCallback(Data * queryData,const SVGTextFragment & fragment) const527 bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const
528 {
529     CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData);
530 
531     FloatRect extent;
532     for (unsigned i = 0; i < fragment.length; ++i) {
533         int startPosition = data->processedCharacters + i;
534         int endPosition = startPosition + 1;
535         if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
536             continue;
537 
538         calculateGlyphBoundaries(queryData, fragment, startPosition, extent);
539         if (extent.contains(data->position)) {
540             data->processedCharacters += i;
541             return true;
542         }
543     }
544 
545     return false;
546 }
547 
characterNumberAtPosition(const FloatPoint & position) const548 int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const
549 {
550     if (m_textBoxes.isEmpty())
551         return -1;
552 
553     CharacterNumberAtPositionData data(position);
554     if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback))
555         return -1;
556 
557     return data.processedCharacters;
558 }
559 
560 }
561 
562 #endif
563