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
32namespace WebCore {
33
34// Base structure for callback user data
35struct 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
50static 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
80SVGTextQuery::SVGTextQuery(RenderObject* renderer)
81{
82    collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
83}
84
85void 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
105bool 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
138bool 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
157void 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
228bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const
229{
230    // no-op
231    return false;
232}
233
234unsigned 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
245struct TextLengthData : SVGTextQuery::Data {
246    TextLengthData()
247        : textLength(0)
248    {
249    }
250
251    float textLength;
252};
253
254bool 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
261float 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
272struct SubStringLengthData : SVGTextQuery::Data {
273    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
286bool 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
300float 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
311struct StartPositionOfCharacterData : SVGTextQuery::Data {
312    StartPositionOfCharacterData(unsigned queryPosition)
313        : position(queryPosition)
314    {
315    }
316
317    unsigned position;
318    FloatPoint startPosition;
319};
320
321bool 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
349FloatPoint 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
360struct EndPositionOfCharacterData : SVGTextQuery::Data {
361    EndPositionOfCharacterData(unsigned queryPosition)
362        : position(queryPosition)
363    {
364    }
365
366    unsigned position;
367    FloatPoint endPosition;
368};
369
370bool 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
396FloatPoint 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
407struct RotationOfCharacterData : SVGTextQuery::Data {
408    RotationOfCharacterData(unsigned queryPosition)
409        : position(queryPosition)
410        , rotation(0)
411    {
412    }
413
414    unsigned position;
415    float rotation;
416};
417
418bool 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
439float 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
450struct ExtentOfCharacterData : SVGTextQuery::Data {
451    ExtentOfCharacterData(unsigned queryPosition)
452        : position(queryPosition)
453    {
454    }
455
456    unsigned position;
457    FloatRect extent;
458};
459
460static 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
484bool 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
497FloatRect 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
508struct CharacterNumberAtPositionData : SVGTextQuery::Data {
509    CharacterNumberAtPositionData(const FloatPoint& queryPosition)
510        : position(queryPosition)
511    {
512    }
513
514    FloatPoint position;
515};
516
517bool 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
538int 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