• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "core/rendering/svg/SVGTextQuery.h"
22 
23 #include "core/rendering/InlineFlowBox.h"
24 #include "core/rendering/RenderBlock.h"
25 #include "core/rendering/RenderInline.h"
26 #include "core/rendering/svg/RenderSVGInlineText.h"
27 #include "core/rendering/svg/SVGInlineTextBox.h"
28 #include "core/rendering/svg/SVGTextMetrics.h"
29 #include "platform/FloatConversion.h"
30 #include "wtf/MathExtras.h"
31 
32 namespace WebCore {
33 
34 // Base structure for callback user data
35 struct SVGTextQuery::Data {
DataWebCore::SVGTextQuery::Data36     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 
flowBoxForRenderer(RenderObject * renderer)50 static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer)
51 {
52     if (!renderer)
53         return 0;
54 
55     if (renderer->isRenderBlock()) {
56         // If we're given a block element, it has to be a RenderSVGText.
57         ASSERT(renderer->isSVGText());
58         RenderBlock* renderBlock = toRenderBlock(renderer);
59 
60         // RenderSVGText only ever contains a single line box.
61         InlineFlowBox* flowBox = renderBlock->firstLineBox();
62         ASSERT(flowBox == renderBlock->lastLineBox());
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 
SVGTextQuery(RenderObject * renderer)80 SVGTextQuery::SVGTextQuery(RenderObject* renderer)
81 {
82     collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
83 }
84 
collectTextBoxesInFlowBox(InlineFlowBox * flowBox)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 
executeQuery(Data * queryData,ProcessTextFragmentCallback fragmentCallback) const105 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 = &toRenderSVGInlineText(queryData->textBox->textRenderer());
116         ASSERT(queryData->textRenderer->style());
117         ASSERT(queryData->textRenderer->style()->svgStyle());
118 
119         queryData->isVerticalText = queryData->textRenderer->style()->svgStyle()->isVerticalWritingMode();
120         const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments();
121 
122         // Loop over all text fragments in this text box, firing a callback for each.
123         unsigned fragmentCount = fragments.size();
124         for (unsigned i = 0; i < fragmentCount; ++i) {
125             const SVGTextFragment& fragment = fragments.at(i);
126             if ((this->*fragmentCallback)(queryData, fragment))
127                 return true;
128 
129             processedCharacters += fragment.length;
130         }
131 
132         queryData->processedCharacters = processedCharacters;
133     }
134 
135     return false;
136 }
137 
mapStartEndPositionsIntoFragmentCoordinates(Data * queryData,const SVGTextFragment & fragment,int & startPosition,int & endPosition) const138 bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const
139 {
140     // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text fragment.
141     startPosition -= queryData->processedCharacters;
142     endPosition -= queryData->processedCharacters;
143 
144     startPosition = max(0, startPosition);
145 
146     if (startPosition >= endPosition)
147         return false;
148 
149     modifyStartEndPositionsRespectingLigatures(queryData, startPosition, endPosition);
150     if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition))
151         return false;
152 
153     ASSERT(startPosition < endPosition);
154     return true;
155 }
156 
modifyStartEndPositionsRespectingLigatures(Data * queryData,int & startPosition,int & endPosition) const157 void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, int& startPosition, int& endPosition) const
158 {
159     SVGTextLayoutAttributes* layoutAttributes = queryData->textRenderer->layoutAttributes();
160     Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes->textMetricsValues();
161     unsigned boxStart = queryData->textBox->start();
162     unsigned boxLength = queryData->textBox->len();
163 
164     unsigned textMetricsOffset = 0;
165     unsigned textMetricsSize = textMetricsValues.size();
166 
167     unsigned positionOffset = 0;
168     unsigned positionSize = layoutAttributes->context()->textLength();
169 
170     bool alterStartPosition = true;
171     bool alterEndPosition = true;
172 
173     int lastPositionOffset = -1;
174     for (; textMetricsOffset < textMetricsSize && positionOffset < positionSize; ++textMetricsOffset) {
175         SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset];
176 
177         // Advance to text box start location.
178         if (positionOffset < boxStart) {
179             positionOffset += metrics.length();
180             continue;
181         }
182 
183         // Stop if we've finished processing this text box.
184         if (positionOffset >= boxStart + boxLength)
185             break;
186 
187         // If the start position maps to a character in the metrics list, we don't need to modify it.
188         if (startPosition == static_cast<int>(positionOffset))
189             alterStartPosition = false;
190 
191         // If the start position maps to a character in the metrics list, we don't need to modify it.
192         if (endPosition == static_cast<int>(positionOffset))
193             alterEndPosition = false;
194 
195         // Detect ligatures.
196         if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
197             if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) {
198                 startPosition = lastPositionOffset;
199                 alterStartPosition = false;
200             }
201 
202             if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) {
203                 endPosition = positionOffset;
204                 alterEndPosition = false;
205             }
206         }
207 
208         if (!alterStartPosition && !alterEndPosition)
209             break;
210 
211         lastPositionOffset = positionOffset;
212         positionOffset += metrics.length();
213     }
214 
215     if (!alterStartPosition && !alterEndPosition)
216         return;
217 
218     if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
219         if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset))
220             startPosition = lastPositionOffset;
221 
222         if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset))
223             endPosition = positionOffset;
224     }
225 }
226 
227 // numberOfCharacters() implementation
numberOfCharactersCallback(Data *,const SVGTextFragment &) const228 bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const
229 {
230     // no-op
231     return false;
232 }
233 
numberOfCharacters() const234 unsigned SVGTextQuery::numberOfCharacters() const
235 {
236     if (m_textBoxes.isEmpty())
237         return 0;
238 
239     Data data;
240     executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback);
241     return data.processedCharacters;
242 }
243 
244 // textLength() implementation
245 struct TextLengthData : SVGTextQuery::Data {
TextLengthDataWebCore::TextLengthData246     TextLengthData()
247         : textLength(0)
248     {
249     }
250 
251     float textLength;
252 };
253 
textLengthCallback(Data * queryData,const SVGTextFragment & fragment) const254 bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
255 {
256     TextLengthData* data = static_cast<TextLengthData*>(queryData);
257     data->textLength += queryData->isVerticalText ? fragment.height : fragment.width;
258     return false;
259 }
260 
textLength() const261 float SVGTextQuery::textLength() const
262 {
263     if (m_textBoxes.isEmpty())
264         return 0;
265 
266     TextLengthData data;
267     executeQuery(&data, &SVGTextQuery::textLengthCallback);
268     return data.textLength;
269 }
270 
271 // subStringLength() implementation
272 struct SubStringLengthData : SVGTextQuery::Data {
SubStringLengthDataWebCore::SubStringLengthData273     SubStringLengthData(unsigned queryStartPosition, unsigned queryLength)
274         : startPosition(queryStartPosition)
275         , length(queryLength)
276         , subStringLength(0)
277     {
278     }
279 
280     unsigned startPosition;
281     unsigned length;
282 
283     float subStringLength;
284 };
285 
subStringLengthCallback(Data * queryData,const SVGTextFragment & fragment) const286 bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
287 {
288     SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData);
289 
290     int startPosition = data->startPosition;
291     int endPosition = startPosition + data->length;
292     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
293         return false;
294 
295     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, endPosition - startPosition);
296     data->subStringLength += queryData->isVerticalText ? metrics.height() : metrics.width();
297     return false;
298 }
299 
subStringLength(unsigned startPosition,unsigned length) const300 float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const
301 {
302     if (m_textBoxes.isEmpty())
303         return 0;
304 
305     SubStringLengthData data(startPosition, length);
306     executeQuery(&data, &SVGTextQuery::subStringLengthCallback);
307     return data.subStringLength;
308 }
309 
310 // startPositionOfCharacter() implementation
311 struct StartPositionOfCharacterData : SVGTextQuery::Data {
StartPositionOfCharacterDataWebCore::StartPositionOfCharacterData312     StartPositionOfCharacterData(unsigned queryPosition)
313         : position(queryPosition)
314     {
315     }
316 
317     unsigned position;
318     FloatPoint startPosition;
319 };
320 
startPositionOfCharacterCallback(Data * queryData,const SVGTextFragment & fragment) const321 bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
322 {
323     StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData);
324 
325     int startPosition = data->position;
326     int endPosition = startPosition + 1;
327     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
328         return false;
329 
330     data->startPosition = FloatPoint(fragment.x, fragment.y);
331 
332     if (startPosition) {
333         SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition);
334         if (queryData->isVerticalText)
335             data->startPosition.move(0, metrics.height());
336         else
337             data->startPosition.move(metrics.width(), 0);
338     }
339 
340     AffineTransform fragmentTransform;
341     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
342     if (fragmentTransform.isIdentity())
343         return true;
344 
345     data->startPosition = fragmentTransform.mapPoint(data->startPosition);
346     return true;
347 }
348 
startPositionOfCharacter(unsigned position) const349 FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const
350 {
351     if (m_textBoxes.isEmpty())
352         return FloatPoint();
353 
354     StartPositionOfCharacterData data(position);
355     executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback);
356     return data.startPosition;
357 }
358 
359 // endPositionOfCharacter() implementation
360 struct EndPositionOfCharacterData : SVGTextQuery::Data {
EndPositionOfCharacterDataWebCore::EndPositionOfCharacterData361     EndPositionOfCharacterData(unsigned queryPosition)
362         : position(queryPosition)
363     {
364     }
365 
366     unsigned position;
367     FloatPoint endPosition;
368 };
369 
endPositionOfCharacterCallback(Data * queryData,const SVGTextFragment & fragment) const370 bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
371 {
372     EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData);
373 
374     int startPosition = data->position;
375     int endPosition = startPosition + 1;
376     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
377         return false;
378 
379     data->endPosition = FloatPoint(fragment.x, fragment.y);
380 
381     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition + 1);
382     if (queryData->isVerticalText)
383         data->endPosition.move(0, metrics.height());
384     else
385         data->endPosition.move(metrics.width(), 0);
386 
387     AffineTransform fragmentTransform;
388     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
389     if (fragmentTransform.isIdentity())
390         return true;
391 
392     data->endPosition = fragmentTransform.mapPoint(data->endPosition);
393     return true;
394 }
395 
endPositionOfCharacter(unsigned position) const396 FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const
397 {
398     if (m_textBoxes.isEmpty())
399         return FloatPoint();
400 
401     EndPositionOfCharacterData data(position);
402     executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback);
403     return data.endPosition;
404 }
405 
406 // rotationOfCharacter() implementation
407 struct RotationOfCharacterData : SVGTextQuery::Data {
RotationOfCharacterDataWebCore::RotationOfCharacterData408     RotationOfCharacterData(unsigned queryPosition)
409         : position(queryPosition)
410         , rotation(0)
411     {
412     }
413 
414     unsigned position;
415     float rotation;
416 };
417 
rotationOfCharacterCallback(Data * queryData,const SVGTextFragment & fragment) const418 bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
419 {
420     RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData);
421 
422     int startPosition = data->position;
423     int endPosition = startPosition + 1;
424     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
425         return false;
426 
427     AffineTransform fragmentTransform;
428     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
429     if (fragmentTransform.isIdentity())
430         data->rotation = 0;
431     else {
432         fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale());
433         data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a())));
434     }
435 
436     return true;
437 }
438 
rotationOfCharacter(unsigned position) const439 float SVGTextQuery::rotationOfCharacter(unsigned position) const
440 {
441     if (m_textBoxes.isEmpty())
442         return 0;
443 
444     RotationOfCharacterData data(position);
445     executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback);
446     return data.rotation;
447 }
448 
449 // extentOfCharacter() implementation
450 struct ExtentOfCharacterData : SVGTextQuery::Data {
ExtentOfCharacterDataWebCore::ExtentOfCharacterData451     ExtentOfCharacterData(unsigned queryPosition)
452         : position(queryPosition)
453     {
454     }
455 
456     unsigned position;
457     FloatRect extent;
458 };
459 
calculateGlyphBoundaries(SVGTextQuery::Data * queryData,const SVGTextFragment & fragment,int startPosition,FloatRect & extent)460 static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent)
461 {
462     float scalingFactor = queryData->textRenderer->scalingFactor();
463     ASSERT(scalingFactor);
464 
465     extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->scaledFont().fontMetrics().floatAscent() / scalingFactor));
466 
467     if (startPosition) {
468         SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition);
469         if (queryData->isVerticalText)
470             extent.move(0, metrics.height());
471         else
472             extent.move(metrics.width(), 0);
473     }
474 
475     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, 1);
476     extent.setSize(FloatSize(metrics.width(), metrics.height()));
477 
478     AffineTransform fragmentTransform;
479     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
480 
481     extent = fragmentTransform.mapRect(extent);
482 }
483 
extentOfCharacterCallback(Data * queryData,const SVGTextFragment & fragment) const484 bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
485 {
486     ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData);
487 
488     int startPosition = data->position;
489     int endPosition = startPosition + 1;
490     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
491         return false;
492 
493     calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent);
494     return true;
495 }
496 
extentOfCharacter(unsigned position) const497 FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const
498 {
499     if (m_textBoxes.isEmpty())
500         return FloatRect();
501 
502     ExtentOfCharacterData data(position);
503     executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback);
504     return data.extent;
505 }
506 
507 // characterNumberAtPosition() implementation
508 struct CharacterNumberAtPositionData : SVGTextQuery::Data {
CharacterNumberAtPositionDataWebCore::CharacterNumberAtPositionData509     CharacterNumberAtPositionData(const FloatPoint& queryPosition)
510         : position(queryPosition)
511     {
512     }
513 
514     FloatPoint position;
515 };
516 
characterNumberAtPositionCallback(Data * queryData,const SVGTextFragment & fragment) const517 bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const
518 {
519     CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData);
520 
521     FloatRect extent;
522     for (unsigned i = 0; i < fragment.length; ++i) {
523         int startPosition = data->processedCharacters + i;
524         int endPosition = startPosition + 1;
525         if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
526             continue;
527 
528         calculateGlyphBoundaries(queryData, fragment, startPosition, extent);
529         if (extent.contains(data->position)) {
530             data->processedCharacters += i;
531             return true;
532         }
533     }
534 
535     return false;
536 }
537 
characterNumberAtPosition(const FloatPoint & position) const538 int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const
539 {
540     if (m_textBoxes.isEmpty())
541         return -1;
542 
543     CharacterNumberAtPositionData data(position);
544     if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback))
545         return -1;
546 
547     return data.processedCharacters;
548 }
549 
550 }
551