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