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
35namespace WebCore {
36
37// Base structure for callback user data
38struct SVGTextQuery::Data {
39    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
53static 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
83SVGTextQuery::SVGTextQuery(RenderObject* renderer)
84{
85    collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
86}
87
88void 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
108bool 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
142bool 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
159void 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
236bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const
237{
238    // no-op
239    return false;
240}
241
242unsigned 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
253struct TextLengthData : SVGTextQuery::Data {
254    TextLengthData()
255        : textLength(0)
256    {
257    }
258
259    float textLength;
260};
261
262bool 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
269float 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
280struct SubStringLengthData : SVGTextQuery::Data {
281    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
294bool 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
308float 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
319struct StartPositionOfCharacterData : SVGTextQuery::Data {
320    StartPositionOfCharacterData(unsigned queryPosition)
321        : position(queryPosition)
322    {
323    }
324
325    unsigned position;
326    FloatPoint startPosition;
327};
328
329bool 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
357FloatPoint 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
368struct EndPositionOfCharacterData : SVGTextQuery::Data {
369    EndPositionOfCharacterData(unsigned queryPosition)
370        : position(queryPosition)
371    {
372    }
373
374    unsigned position;
375    FloatPoint endPosition;
376};
377
378bool 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
404FloatPoint 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
415struct RotationOfCharacterData : SVGTextQuery::Data {
416    RotationOfCharacterData(unsigned queryPosition)
417        : position(queryPosition)
418        , rotation(0)
419    {
420    }
421
422    unsigned position;
423    float rotation;
424};
425
426bool 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
447float 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
458struct ExtentOfCharacterData : SVGTextQuery::Data {
459    ExtentOfCharacterData(unsigned queryPosition)
460        : position(queryPosition)
461    {
462    }
463
464    unsigned position;
465    FloatRect extent;
466};
467
468static 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
494bool 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
507FloatRect 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
518struct CharacterNumberAtPositionData : SVGTextQuery::Data {
519    CharacterNumberAtPositionData(const FloatPoint& queryPosition)
520        : position(queryPosition)
521    {
522    }
523
524    FloatPoint position;
525};
526
527bool 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
548int 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