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