Home | History | Annotate | Download | only in svg
      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/platform/FloatConversion.h"
     24 #include "core/rendering/InlineFlowBox.h"
     25 #include "core/rendering/RenderBlock.h"
     26 #include "core/rendering/RenderInline.h"
     27 #include "core/rendering/svg/RenderSVGInlineText.h"
     28 #include "core/rendering/svg/SVGInlineTextBox.h"
     29 #include "core/rendering/svg/SVGTextMetrics.h"
     30 
     31 #include "wtf/MathExtras.h"
     32 
     33 namespace WebCore {
     34 
     35 // Base structure for callback user data
     36 struct SVGTextQuery::Data {
     37     Data()
     38         : isVerticalText(false)
     39         , processedCharacters(0)
     40         , textRenderer(0)
     41         , textBox(0)
     42     {
     43     }
     44 
     45     bool isVerticalText;
     46     unsigned processedCharacters;
     47     RenderSVGInlineText* textRenderer;
     48     const SVGInlineTextBox* textBox;
     49 };
     50 
     51 static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer)
     52 {
     53     if (!renderer)
     54         return 0;
     55 
     56     if (renderer->isRenderBlock()) {
     57         // If we're given a block element, it has to be a RenderSVGText.
     58         ASSERT(renderer->isSVGText());
     59         RenderBlock* renderBlock = toRenderBlock(renderer);
     60 
     61         // RenderSVGText only ever contains a single line box.
     62         InlineFlowBox* flowBox = renderBlock->firstLineBox();
     63         ASSERT(flowBox == renderBlock->lastLineBox());
     64         return flowBox;
     65     }
     66 
     67     if (renderer->isRenderInline()) {
     68         // We're given a RenderSVGInline or objects that derive from it (RenderSVGTSpan / RenderSVGTextPath)
     69         RenderInline* renderInline = toRenderInline(renderer);
     70 
     71         // RenderSVGInline only ever contains a single line box.
     72         InlineFlowBox* flowBox = renderInline->firstLineBox();
     73         ASSERT(flowBox == renderInline->lastLineBox());
     74         return flowBox;
     75     }
     76 
     77     ASSERT_NOT_REACHED();
     78     return 0;
     79 }
     80 
     81 SVGTextQuery::SVGTextQuery(RenderObject* renderer)
     82 {
     83     collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
     84 }
     85 
     86 void SVGTextQuery::collectTextBoxesInFlowBox(InlineFlowBox* flowBox)
     87 {
     88     if (!flowBox)
     89         return;
     90 
     91     for (InlineBox* child = flowBox->firstChild(); child; child = child->nextOnLine()) {
     92         if (child->isInlineFlowBox()) {
     93             // Skip generated content.
     94             if (!child->renderer()->node())
     95                 continue;
     96 
     97             collectTextBoxesInFlowBox(static_cast<InlineFlowBox*>(child));
     98             continue;
     99         }
    100 
    101         if (child->isSVGInlineTextBox())
    102             m_textBoxes.append(toSVGInlineTextBox(child));
    103     }
    104 }
    105 
    106 bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextFragmentCallback fragmentCallback) const
    107 {
    108     ASSERT(!m_textBoxes.isEmpty());
    109 
    110     unsigned processedCharacters = 0;
    111     unsigned textBoxCount = m_textBoxes.size();
    112 
    113     // Loop over all text boxes
    114     for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) {
    115         queryData->textBox = m_textBoxes.at(textBoxPosition);
    116         queryData->textRenderer = toRenderSVGInlineText(queryData->textBox->textRenderer());
    117         ASSERT(queryData->textRenderer);
    118         ASSERT(queryData->textRenderer->style());
    119         ASSERT(queryData->textRenderer->style()->svgStyle());
    120 
    121         queryData->isVerticalText = queryData->textRenderer->style()->svgStyle()->isVerticalWritingMode();
    122         const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments();
    123 
    124         // Loop over all text fragments in this text box, firing a callback for each.
    125         unsigned fragmentCount = fragments.size();
    126         for (unsigned i = 0; i < fragmentCount; ++i) {
    127             const SVGTextFragment& fragment = fragments.at(i);
    128             if ((this->*fragmentCallback)(queryData, fragment))
    129                 return true;
    130 
    131             processedCharacters += fragment.length;
    132         }
    133 
    134         queryData->processedCharacters = processedCharacters;
    135     }
    136 
    137     return false;
    138 }
    139 
    140 bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const
    141 {
    142     // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text fragment.
    143     startPosition -= queryData->processedCharacters;
    144     endPosition -= queryData->processedCharacters;
    145 
    146     if (startPosition >= endPosition || startPosition < 0 || endPosition < 0)
    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 
    157 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             alterStartPosition = false;
    222         }
    223 
    224         if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) {
    225             endPosition = positionOffset;
    226             alterEndPosition = false;
    227         }
    228     }
    229 }
    230 
    231 // numberOfCharacters() implementation
    232 bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const
    233 {
    234     // no-op
    235     return false;
    236 }
    237 
    238 unsigned SVGTextQuery::numberOfCharacters() const
    239 {
    240     if (m_textBoxes.isEmpty())
    241         return 0;
    242 
    243     Data data;
    244     executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback);
    245     return data.processedCharacters;
    246 }
    247 
    248 // textLength() implementation
    249 struct TextLengthData : SVGTextQuery::Data {
    250     TextLengthData()
    251         : textLength(0)
    252     {
    253     }
    254 
    255     float textLength;
    256 };
    257 
    258 bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
    259 {
    260     TextLengthData* data = static_cast<TextLengthData*>(queryData);
    261     data->textLength += queryData->isVerticalText ? fragment.height : fragment.width;
    262     return false;
    263 }
    264 
    265 float SVGTextQuery::textLength() const
    266 {
    267     if (m_textBoxes.isEmpty())
    268         return 0;
    269 
    270     TextLengthData data;
    271     executeQuery(&data, &SVGTextQuery::textLengthCallback);
    272     return data.textLength;
    273 }
    274 
    275 // subStringLength() implementation
    276 struct SubStringLengthData : SVGTextQuery::Data {
    277     SubStringLengthData(unsigned queryStartPosition, unsigned queryLength)
    278         : startPosition(queryStartPosition)
    279         , length(queryLength)
    280         , subStringLength(0)
    281     {
    282     }
    283 
    284     unsigned startPosition;
    285     unsigned length;
    286 
    287     float subStringLength;
    288 };
    289 
    290 bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
    291 {
    292     SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData);
    293 
    294     int startPosition = data->startPosition;
    295     int endPosition = startPosition + data->length;
    296     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    297         return false;
    298 
    299     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, endPosition - startPosition);
    300     data->subStringLength += queryData->isVerticalText ? metrics.height() : metrics.width();
    301     return false;
    302 }
    303 
    304 float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const
    305 {
    306     if (m_textBoxes.isEmpty())
    307         return 0;
    308 
    309     SubStringLengthData data(startPosition, length);
    310     executeQuery(&data, &SVGTextQuery::subStringLengthCallback);
    311     return data.subStringLength;
    312 }
    313 
    314 // startPositionOfCharacter() implementation
    315 struct StartPositionOfCharacterData : SVGTextQuery::Data {
    316     StartPositionOfCharacterData(unsigned queryPosition)
    317         : position(queryPosition)
    318     {
    319     }
    320 
    321     unsigned position;
    322     FloatPoint startPosition;
    323 };
    324 
    325 bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
    326 {
    327     StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData);
    328 
    329     int startPosition = data->position;
    330     int endPosition = startPosition + 1;
    331     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    332         return false;
    333 
    334     data->startPosition = FloatPoint(fragment.x, fragment.y);
    335 
    336     if (startPosition) {
    337         SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition);
    338         if (queryData->isVerticalText)
    339             data->startPosition.move(0, metrics.height());
    340         else
    341             data->startPosition.move(metrics.width(), 0);
    342     }
    343 
    344     AffineTransform fragmentTransform;
    345     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
    346     if (fragmentTransform.isIdentity())
    347         return true;
    348 
    349     data->startPosition = fragmentTransform.mapPoint(data->startPosition);
    350     return true;
    351 }
    352 
    353 FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const
    354 {
    355     if (m_textBoxes.isEmpty())
    356         return FloatPoint();
    357 
    358     StartPositionOfCharacterData data(position);
    359     executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback);
    360     return data.startPosition;
    361 }
    362 
    363 // endPositionOfCharacter() implementation
    364 struct EndPositionOfCharacterData : SVGTextQuery::Data {
    365     EndPositionOfCharacterData(unsigned queryPosition)
    366         : position(queryPosition)
    367     {
    368     }
    369 
    370     unsigned position;
    371     FloatPoint endPosition;
    372 };
    373 
    374 bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
    375 {
    376     EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData);
    377 
    378     int startPosition = data->position;
    379     int endPosition = startPosition + 1;
    380     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    381         return false;
    382 
    383     data->endPosition = FloatPoint(fragment.x, fragment.y);
    384 
    385     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition + 1);
    386     if (queryData->isVerticalText)
    387         data->endPosition.move(0, metrics.height());
    388     else
    389         data->endPosition.move(metrics.width(), 0);
    390 
    391     AffineTransform fragmentTransform;
    392     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
    393     if (fragmentTransform.isIdentity())
    394         return true;
    395 
    396     data->endPosition = fragmentTransform.mapPoint(data->endPosition);
    397     return true;
    398 }
    399 
    400 FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const
    401 {
    402     if (m_textBoxes.isEmpty())
    403         return FloatPoint();
    404 
    405     EndPositionOfCharacterData data(position);
    406     executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback);
    407     return data.endPosition;
    408 }
    409 
    410 // rotationOfCharacter() implementation
    411 struct RotationOfCharacterData : SVGTextQuery::Data {
    412     RotationOfCharacterData(unsigned queryPosition)
    413         : position(queryPosition)
    414         , rotation(0)
    415     {
    416     }
    417 
    418     unsigned position;
    419     float rotation;
    420 };
    421 
    422 bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
    423 {
    424     RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData);
    425 
    426     int startPosition = data->position;
    427     int endPosition = startPosition + 1;
    428     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    429         return false;
    430 
    431     AffineTransform fragmentTransform;
    432     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
    433     if (fragmentTransform.isIdentity())
    434         data->rotation = 0;
    435     else {
    436         fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale());
    437         data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a())));
    438     }
    439 
    440     return true;
    441 }
    442 
    443 float SVGTextQuery::rotationOfCharacter(unsigned position) const
    444 {
    445     if (m_textBoxes.isEmpty())
    446         return 0;
    447 
    448     RotationOfCharacterData data(position);
    449     executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback);
    450     return data.rotation;
    451 }
    452 
    453 // extentOfCharacter() implementation
    454 struct ExtentOfCharacterData : SVGTextQuery::Data {
    455     ExtentOfCharacterData(unsigned queryPosition)
    456         : position(queryPosition)
    457     {
    458     }
    459 
    460     unsigned position;
    461     FloatRect extent;
    462 };
    463 
    464 static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent)
    465 {
    466     float scalingFactor = queryData->textRenderer->scalingFactor();
    467     ASSERT(scalingFactor);
    468 
    469     extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->scaledFont().fontMetrics().floatAscent() / scalingFactor));
    470 
    471     if (startPosition) {
    472         SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition);
    473         if (queryData->isVerticalText)
    474             extent.move(0, metrics.height());
    475         else
    476             extent.move(metrics.width(), 0);
    477     }
    478 
    479     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, 1);
    480     extent.setSize(FloatSize(metrics.width(), metrics.height()));
    481 
    482     AffineTransform fragmentTransform;
    483     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
    484     if (fragmentTransform.isIdentity())
    485         return;
    486 
    487     extent = fragmentTransform.mapRect(extent);
    488 }
    489 
    490 bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
    491 {
    492     ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData);
    493 
    494     int startPosition = data->position;
    495     int endPosition = startPosition + 1;
    496     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    497         return false;
    498 
    499     calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent);
    500     return true;
    501 }
    502 
    503 SVGRect SVGTextQuery::extentOfCharacter(unsigned position) const
    504 {
    505     if (m_textBoxes.isEmpty())
    506         return SVGRect();
    507 
    508     ExtentOfCharacterData data(position);
    509     executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback);
    510     return data.extent;
    511 }
    512 
    513 // characterNumberAtPosition() implementation
    514 struct CharacterNumberAtPositionData : SVGTextQuery::Data {
    515     CharacterNumberAtPositionData(const FloatPoint& queryPosition)
    516         : position(queryPosition)
    517     {
    518     }
    519 
    520     FloatPoint position;
    521 };
    522 
    523 bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const
    524 {
    525     CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData);
    526 
    527     FloatRect extent;
    528     for (unsigned i = 0; i < fragment.length; ++i) {
    529         int startPosition = data->processedCharacters + i;
    530         int endPosition = startPosition + 1;
    531         if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    532             continue;
    533 
    534         calculateGlyphBoundaries(queryData, fragment, startPosition, extent);
    535         if (extent.contains(data->position)) {
    536             data->processedCharacters += i;
    537             return true;
    538         }
    539     }
    540 
    541     return false;
    542 }
    543 
    544 int SVGTextQuery::characterNumberAtPosition(const SVGPoint& position) const
    545 {
    546     if (m_textBoxes.isEmpty())
    547         return -1;
    548 
    549     CharacterNumberAtPositionData data(position);
    550     if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback))
    551         return -1;
    552 
    553     return data.processedCharacters;
    554 }
    555 
    556 }
    557