Home | History | Annotate | Download | only in svg
      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 {
     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 
     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 
     83 SVGTextQuery::SVGTextQuery(RenderObject* renderer)
     84 {
     85     collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
     86 }
     87 
     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 
    108 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 
    142 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 
    159 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
    236 bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const
    237 {
    238     // no-op
    239     return false;
    240 }
    241 
    242 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 {
    254     TextLengthData()
    255         : textLength(0)
    256     {
    257     }
    258 
    259     float textLength;
    260 };
    261 
    262 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 
    269 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 {
    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 
    294 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 
    308 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 {
    320     StartPositionOfCharacterData(unsigned queryPosition)
    321         : position(queryPosition)
    322     {
    323     }
    324 
    325     unsigned position;
    326     FloatPoint startPosition;
    327 };
    328 
    329 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 
    357 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 {
    369     EndPositionOfCharacterData(unsigned queryPosition)
    370         : position(queryPosition)
    371     {
    372     }
    373 
    374     unsigned position;
    375     FloatPoint endPosition;
    376 };
    377 
    378 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 
    404 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 {
    416     RotationOfCharacterData(unsigned queryPosition)
    417         : position(queryPosition)
    418         , rotation(0)
    419     {
    420     }
    421 
    422     unsigned position;
    423     float rotation;
    424 };
    425 
    426 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 
    447 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 {
    459     ExtentOfCharacterData(unsigned queryPosition)
    460         : position(queryPosition)
    461     {
    462     }
    463 
    464     unsigned position;
    465     FloatRect extent;
    466 };
    467 
    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 
    494 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 
    507 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 {
    519     CharacterNumberAtPositionData(const FloatPoint& queryPosition)
    520         : position(queryPosition)
    521     {
    522     }
    523 
    524     FloatPoint position;
    525 };
    526 
    527 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 
    548 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