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