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