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/rendering/InlineFlowBox.h"
     24 #include "core/rendering/RenderBlockFlow.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 blink {
     33 
     34 // Base structure for callback user data
     35 struct 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 
     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         RenderBlockFlow* renderBlockFlow = toRenderBlockFlow(renderer);
     59 
     60         // RenderSVGText only ever contains a single line box.
     61         InlineFlowBox* flowBox = renderBlockFlow->firstLineBox();
     62         ASSERT(flowBox == renderBlockFlow->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 
     80 SVGTextQuery::SVGTextQuery(RenderObject* renderer)
     81 {
     82     collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
     83 }
     84 
     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 
    105 bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextFragmentCallback fragmentCallback) const
    106 {
    107     unsigned processedCharacters = 0;
    108     unsigned textBoxCount = m_textBoxes.size();
    109 
    110     // Loop over all text boxes
    111     for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) {
    112         queryData->textBox = m_textBoxes.at(textBoxPosition);
    113         queryData->textRenderer = &toRenderSVGInlineText(queryData->textBox->renderer());
    114         ASSERT(queryData->textRenderer->style());
    115 
    116         queryData->isVerticalText = queryData->textRenderer->style()->svgStyle().isVerticalWritingMode();
    117         const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments();
    118 
    119         // Loop over all text fragments in this text box, firing a callback for each.
    120         unsigned fragmentCount = fragments.size();
    121         for (unsigned i = 0; i < fragmentCount; ++i) {
    122             const SVGTextFragment& fragment = fragments.at(i);
    123             if ((this->*fragmentCallback)(queryData, fragment))
    124                 return true;
    125 
    126             processedCharacters += fragment.length;
    127         }
    128 
    129         queryData->processedCharacters = processedCharacters;
    130     }
    131 
    132     return false;
    133 }
    134 
    135 bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const
    136 {
    137     // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text fragment.
    138     startPosition -= queryData->processedCharacters;
    139     endPosition -= queryData->processedCharacters;
    140 
    141     // <startPosition, endPosition> is now a tuple of offsets relative to the current text box.
    142     // Compute the offsets of the fragment in the same offset space.
    143     int fragmentStartInBox = fragment.characterOffset - queryData->textBox->start();
    144     int fragmentEndInBox = fragmentStartInBox + fragment.length;
    145 
    146     // Check if the ranges intersect.
    147     startPosition = std::max(startPosition, fragmentStartInBox);
    148     endPosition = std::min(endPosition, fragmentEndInBox);
    149 
    150     if (startPosition >= endPosition)
    151         return false;
    152 
    153     modifyStartEndPositionsRespectingLigatures(queryData, fragment, startPosition, endPosition);
    154     if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition))
    155         return false;
    156 
    157     ASSERT(startPosition < endPosition);
    158     return true;
    159 }
    160 
    161 void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const
    162 {
    163     SVGTextLayoutAttributes* layoutAttributes = queryData->textRenderer->layoutAttributes();
    164     Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes->textMetricsValues();
    165 
    166     unsigned textMetricsOffset = fragment.metricsListOffset;
    167 
    168     // Compute the offset of the fragment within the box, since that's the
    169     // space <startPosition, endPosition> is in (and that's what we need).
    170     int fragmentOffsetInBox = fragment.characterOffset - queryData->textBox->start();
    171     int fragmentEndInBox = fragmentOffsetInBox + fragment.length;
    172 
    173     // Find the text metrics cell that start at or contain the character startPosition.
    174     while (fragmentOffsetInBox < fragmentEndInBox) {
    175         SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset];
    176         int glyphEnd = fragmentOffsetInBox + metrics.length();
    177         if (startPosition < glyphEnd)
    178             break;
    179         fragmentOffsetInBox = glyphEnd;
    180         textMetricsOffset++;
    181     }
    182 
    183     startPosition = fragmentOffsetInBox;
    184 
    185     // Find the text metrics cell that contain or ends at the character endPosition.
    186     while (fragmentOffsetInBox < fragmentEndInBox) {
    187         SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset];
    188         fragmentOffsetInBox += metrics.length();
    189         if (fragmentOffsetInBox >= endPosition)
    190             break;
    191         textMetricsOffset++;
    192     }
    193 
    194     endPosition = fragmentOffsetInBox;
    195 }
    196 
    197 // numberOfCharacters() implementation
    198 bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const
    199 {
    200     // no-op
    201     return false;
    202 }
    203 
    204 unsigned SVGTextQuery::numberOfCharacters() const
    205 {
    206     Data data;
    207     executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback);
    208     return data.processedCharacters;
    209 }
    210 
    211 // textLength() implementation
    212 struct TextLengthData : SVGTextQuery::Data {
    213     TextLengthData()
    214         : textLength(0)
    215     {
    216     }
    217 
    218     float textLength;
    219 };
    220 
    221 bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
    222 {
    223     TextLengthData* data = static_cast<TextLengthData*>(queryData);
    224     data->textLength += queryData->isVerticalText ? fragment.height : fragment.width;
    225     return false;
    226 }
    227 
    228 float SVGTextQuery::textLength() const
    229 {
    230     TextLengthData data;
    231     executeQuery(&data, &SVGTextQuery::textLengthCallback);
    232     return data.textLength;
    233 }
    234 
    235 // subStringLength() implementation
    236 struct SubStringLengthData : SVGTextQuery::Data {
    237     SubStringLengthData(unsigned queryStartPosition, unsigned queryLength)
    238         : startPosition(queryStartPosition)
    239         , length(queryLength)
    240         , subStringLength(0)
    241     {
    242     }
    243 
    244     unsigned startPosition;
    245     unsigned length;
    246 
    247     float subStringLength;
    248 };
    249 
    250 bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
    251 {
    252     SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData);
    253 
    254     int startPosition = data->startPosition;
    255     int endPosition = startPosition + data->length;
    256     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    257         return false;
    258 
    259     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, endPosition - startPosition);
    260     data->subStringLength += queryData->isVerticalText ? metrics.height() : metrics.width();
    261     return false;
    262 }
    263 
    264 float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const
    265 {
    266     SubStringLengthData data(startPosition, length);
    267     executeQuery(&data, &SVGTextQuery::subStringLengthCallback);
    268     return data.subStringLength;
    269 }
    270 
    271 // startPositionOfCharacter() implementation
    272 struct StartPositionOfCharacterData : SVGTextQuery::Data {
    273     StartPositionOfCharacterData(unsigned queryPosition)
    274         : position(queryPosition)
    275     {
    276     }
    277 
    278     unsigned position;
    279     FloatPoint startPosition;
    280 };
    281 
    282 bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
    283 {
    284     StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData);
    285 
    286     int startPosition = data->position;
    287     int endPosition = startPosition + 1;
    288     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    289         return false;
    290 
    291     data->startPosition = FloatPoint(fragment.x, fragment.y);
    292 
    293     if (startPosition) {
    294         SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition);
    295         if (queryData->isVerticalText)
    296             data->startPosition.move(0, metrics.height());
    297         else
    298             data->startPosition.move(metrics.width(), 0);
    299     }
    300 
    301     AffineTransform fragmentTransform;
    302     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
    303     if (fragmentTransform.isIdentity())
    304         return true;
    305 
    306     data->startPosition = fragmentTransform.mapPoint(data->startPosition);
    307     return true;
    308 }
    309 
    310 FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const
    311 {
    312     StartPositionOfCharacterData data(position);
    313     executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback);
    314     return data.startPosition;
    315 }
    316 
    317 // endPositionOfCharacter() implementation
    318 struct EndPositionOfCharacterData : SVGTextQuery::Data {
    319     EndPositionOfCharacterData(unsigned queryPosition)
    320         : position(queryPosition)
    321     {
    322     }
    323 
    324     unsigned position;
    325     FloatPoint endPosition;
    326 };
    327 
    328 bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
    329 {
    330     EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData);
    331 
    332     int startPosition = data->position;
    333     int endPosition = startPosition + 1;
    334     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    335         return false;
    336 
    337     data->endPosition = FloatPoint(fragment.x, fragment.y);
    338 
    339     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition + 1);
    340     if (queryData->isVerticalText)
    341         data->endPosition.move(0, metrics.height());
    342     else
    343         data->endPosition.move(metrics.width(), 0);
    344 
    345     AffineTransform fragmentTransform;
    346     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
    347     if (fragmentTransform.isIdentity())
    348         return true;
    349 
    350     data->endPosition = fragmentTransform.mapPoint(data->endPosition);
    351     return true;
    352 }
    353 
    354 FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const
    355 {
    356     EndPositionOfCharacterData data(position);
    357     executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback);
    358     return data.endPosition;
    359 }
    360 
    361 // rotationOfCharacter() implementation
    362 struct RotationOfCharacterData : SVGTextQuery::Data {
    363     RotationOfCharacterData(unsigned queryPosition)
    364         : position(queryPosition)
    365         , rotation(0)
    366     {
    367     }
    368 
    369     unsigned position;
    370     float rotation;
    371 };
    372 
    373 bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
    374 {
    375     RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData);
    376 
    377     int startPosition = data->position;
    378     int endPosition = startPosition + 1;
    379     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    380         return false;
    381 
    382     AffineTransform fragmentTransform;
    383     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
    384     if (fragmentTransform.isIdentity())
    385         data->rotation = 0;
    386     else {
    387         fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale());
    388         data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a())));
    389     }
    390 
    391     return true;
    392 }
    393 
    394 float SVGTextQuery::rotationOfCharacter(unsigned position) const
    395 {
    396     RotationOfCharacterData data(position);
    397     executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback);
    398     return data.rotation;
    399 }
    400 
    401 // extentOfCharacter() implementation
    402 struct ExtentOfCharacterData : SVGTextQuery::Data {
    403     ExtentOfCharacterData(unsigned queryPosition)
    404         : position(queryPosition)
    405     {
    406     }
    407 
    408     unsigned position;
    409     FloatRect extent;
    410 };
    411 
    412 static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent)
    413 {
    414     float scalingFactor = queryData->textRenderer->scalingFactor();
    415     ASSERT(scalingFactor);
    416 
    417     extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->scaledFont().fontMetrics().floatAscent() / scalingFactor));
    418 
    419     if (startPosition) {
    420         SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset, startPosition);
    421         if (queryData->isVerticalText)
    422             extent.move(0, metrics.height());
    423         else
    424             extent.move(metrics.width(), 0);
    425     }
    426 
    427     SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.characterOffset + startPosition, 1);
    428     extent.setSize(FloatSize(metrics.width(), metrics.height()));
    429 
    430     AffineTransform fragmentTransform;
    431     fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength);
    432 
    433     extent = fragmentTransform.mapRect(extent);
    434 }
    435 
    436 static inline FloatRect calculateFragmentBoundaries(const RenderSVGInlineText& textRenderer, const SVGTextFragment& fragment)
    437 {
    438     float scalingFactor = textRenderer.scalingFactor();
    439     ASSERT(scalingFactor);
    440     float baseline = textRenderer.scaledFont().fontMetrics().floatAscent() / scalingFactor;
    441 
    442     AffineTransform fragmentTransform;
    443     FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height);
    444     fragment.buildFragmentTransform(fragmentTransform);
    445     return fragmentTransform.mapRect(fragmentRect);
    446 }
    447 
    448 bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
    449 {
    450     ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData);
    451 
    452     int startPosition = data->position;
    453     int endPosition = startPosition + 1;
    454     if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
    455         return false;
    456 
    457     calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent);
    458     return true;
    459 }
    460 
    461 FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const
    462 {
    463     ExtentOfCharacterData data(position);
    464     executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback);
    465     return data.extent;
    466 }
    467 
    468 // characterNumberAtPosition() implementation
    469 struct CharacterNumberAtPositionData : SVGTextQuery::Data {
    470     CharacterNumberAtPositionData(const FloatPoint& queryPosition)
    471         : position(queryPosition)
    472     {
    473     }
    474 
    475     FloatPoint position;
    476 };
    477 
    478 bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const
    479 {
    480     CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData);
    481 
    482     // Test the query point against the bounds of the entire fragment first.
    483     FloatRect fragmentExtents = calculateFragmentBoundaries(*queryData->textRenderer, fragment);
    484     if (!fragmentExtents.contains(data->position))
    485         return false;
    486 
    487     // Iterate through the glyphs in this fragment, and check if their extents
    488     // contain the query point.
    489     FloatRect extent;
    490     const Vector<SVGTextMetrics>& textMetrics = queryData->textRenderer->layoutAttributes()->textMetricsValues();
    491     unsigned textMetricsOffset = fragment.metricsListOffset;
    492     unsigned fragmentOffset = 0;
    493     while (fragmentOffset < fragment.length) {
    494         calculateGlyphBoundaries(queryData, fragment, fragmentOffset, extent);
    495         if (extent.contains(data->position)) {
    496             // Compute the character offset of the glyph within the text box
    497             // and add to processedCharacters.
    498             unsigned characterOffset = fragment.characterOffset + fragmentOffset;
    499             data->processedCharacters += characterOffset - data->textBox->start();
    500             return true;
    501         }
    502         fragmentOffset += textMetrics[textMetricsOffset].length();
    503         textMetricsOffset++;
    504     }
    505     return false;
    506 }
    507 
    508 int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const
    509 {
    510     CharacterNumberAtPositionData data(position);
    511     if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback))
    512         return -1;
    513 
    514     return data.processedCharacters;
    515 }
    516 
    517 }
    518