Home | History | Annotate | Download | only in svg
      1 /*
      2     Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann (at) kde.org>
      3                   2004, 2005, 2006, 2007, 2008 Rob Buis <buis (at) kde.org>
      4 
      5     This library is free software; you can redistribute it and/or
      6     modify it under the terms of the GNU Library General Public
      7     License as published by the Free Software Foundation; either
      8     version 2 of the License, or (at your option) any later version.
      9 
     10     This library is distributed in the hope that it will be useful,
     11     but WITHOUT ANY WARRANTY; without even the implied warranty of
     12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13     Library General Public License for more details.
     14 
     15     You should have received a copy of the GNU Library General Public License
     16     along with this library; see the file COPYING.LIB.  If not, write to
     17     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18     Boston, MA 02110-1301, USA.
     19 */
     20 
     21 #include "config.h"
     22 
     23 #if ENABLE(SVG)
     24 #include "SVGTextContentElement.h"
     25 
     26 #include "CSSPropertyNames.h"
     27 #include "CSSValueKeywords.h"
     28 #include "ExceptionCode.h"
     29 #include "FloatPoint.h"
     30 #include "FloatRect.h"
     31 #include "Frame.h"
     32 #include "MappedAttribute.h"
     33 #include "Position.h"
     34 #include "RenderSVGText.h"
     35 #include "SVGCharacterLayoutInfo.h"
     36 #include "SVGInlineTextBox.h"
     37 #include "SVGLength.h"
     38 #include "SVGNames.h"
     39 #include "SVGRootInlineBox.h"
     40 #include "SelectionController.h"
     41 #include "XMLNames.h"
     42 #include <wtf/StdLibExtras.h>
     43 
     44 namespace WebCore {
     45 
     46 SVGTextContentElement::SVGTextContentElement(const QualifiedName& tagName, Document* doc)
     47     : SVGStyledElement(tagName, doc)
     48     , SVGTests()
     49     , SVGLangSpace()
     50     , SVGExternalResourcesRequired()
     51     , m_textLength(LengthModeOther)
     52     , m_lengthAdjust(LENGTHADJUST_SPACING)
     53 {
     54 }
     55 
     56 SVGTextContentElement::~SVGTextContentElement()
     57 {
     58 }
     59 
     60 static inline float cumulativeCharacterRangeLength(const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end, SVGInlineTextBox* textBox,
     61                                                    int startOffset, long startPosition, long length, bool isVerticalText, long& atCharacter)
     62 {
     63     if (!length)
     64         return 0.0f;
     65 
     66     float textLength = 0.0f;
     67     RenderStyle* style = textBox->textRenderer()->style();
     68 
     69     bool usesFullRange = (startPosition == -1 && length == -1);
     70 
     71     for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
     72         if (usesFullRange || (atCharacter >= startPosition && atCharacter <= startPosition + length)) {
     73             unsigned int newOffset = textBox->start() + (it - start) + startOffset;
     74 
     75             // Take RTL text into account and pick right glyph width/height.
     76             if (textBox->direction() == RTL)
     77                 newOffset = textBox->start() + textBox->end() - newOffset;
     78 
     79             // FIXME: does this handle multichar glyphs ok? not sure
     80             int charsConsumed = 0;
     81             String glyphName;
     82             if (isVerticalText)
     83                 textLength += textBox->calculateGlyphHeight(style, newOffset, 0);
     84             else
     85                 textLength += textBox->calculateGlyphWidth(style, newOffset, 0, charsConsumed, glyphName);
     86         }
     87 
     88         if (!usesFullRange) {
     89             if (atCharacter == startPosition + length - 1)
     90                 break;
     91 
     92             atCharacter++;
     93         }
     94     }
     95 
     96     return textLength;
     97 }
     98 
     99 // Helper class for querying certain glyph information
    100 struct SVGInlineTextBoxQueryWalker {
    101     typedef enum {
    102         NumberOfCharacters,
    103         TextLength,
    104         SubStringLength,
    105         StartPosition,
    106         EndPosition,
    107         Extent,
    108         Rotation,
    109         CharacterNumberAtPosition
    110     } QueryMode;
    111 
    112     SVGInlineTextBoxQueryWalker(const SVGTextContentElement* reference, QueryMode mode)
    113         : m_reference(reference)
    114         , m_mode(mode)
    115         , m_queryStartPosition(0)
    116         , m_queryLength(0)
    117         , m_queryPointInput()
    118         , m_queryLongResult(0)
    119         , m_queryFloatResult(0.0f)
    120         , m_queryPointResult()
    121         , m_queryRectResult()
    122         , m_stopProcessing(true)
    123         , m_atCharacter(0)
    124     {
    125     }
    126 
    127     void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const AffineTransform&,
    128                               const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end)
    129     {
    130         RenderStyle* style = textBox->textRenderer()->style();
    131         bool isVerticalText = style->svgStyle()->writingMode() == WM_TBRL || style->svgStyle()->writingMode() == WM_TB;
    132 
    133         switch (m_mode) {
    134         case NumberOfCharacters:
    135         {
    136             m_queryLongResult += (end - start);
    137             m_stopProcessing = false;
    138             return;
    139         }
    140         case TextLength:
    141         {
    142             float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, -1, -1, isVerticalText, m_atCharacter);
    143 
    144             if (isVerticalText)
    145                 m_queryFloatResult += textLength;
    146             else
    147                 m_queryFloatResult += textLength;
    148 
    149             m_stopProcessing = false;
    150             return;
    151         }
    152         case SubStringLength:
    153         {
    154             long startPosition = m_queryStartPosition;
    155             long length = m_queryLength;
    156 
    157             float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, startPosition, length, isVerticalText, m_atCharacter);
    158 
    159             if (isVerticalText)
    160                 m_queryFloatResult += textLength;
    161             else
    162                 m_queryFloatResult += textLength;
    163 
    164             if (m_atCharacter == startPosition + length)
    165                 m_stopProcessing = true;
    166             else
    167                 m_stopProcessing = false;
    168 
    169             return;
    170         }
    171         case StartPosition:
    172         {
    173             for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
    174                 if (m_atCharacter == m_queryStartPosition) {
    175                     m_queryPointResult = FloatPoint(it->x, it->y);
    176                     m_stopProcessing = true;
    177                     return;
    178                 }
    179 
    180                 m_atCharacter++;
    181             }
    182 
    183             m_stopProcessing = false;
    184             return;
    185         }
    186         case EndPosition:
    187         {
    188             for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
    189                 if (m_atCharacter == m_queryStartPosition) {
    190                     unsigned int newOffset = textBox->start() + (it - start) + startOffset;
    191 
    192                     // Take RTL text into account and pick right glyph width/height.
    193                     if (textBox->direction() == RTL)
    194                         newOffset = textBox->start() + textBox->end() - newOffset;
    195 
    196                     int charsConsumed;
    197                     String glyphName;
    198                     // calculateGlyph{Height,Width} will consume at least one character. This is the number of characters available
    199                     // to them beyond that first one.
    200                     int extraCharactersAvailable = end - it - 1;
    201                     if (isVerticalText)
    202                         m_queryPointResult.move(it->x, it->y + textBox->calculateGlyphHeight(style, newOffset, extraCharactersAvailable));
    203                     else
    204                         m_queryPointResult.move(it->x + textBox->calculateGlyphWidth(style, newOffset, extraCharactersAvailable, charsConsumed, glyphName), it->y);
    205 
    206                     m_stopProcessing = true;
    207                     return;
    208                 }
    209 
    210                 m_atCharacter++;
    211             }
    212 
    213             m_stopProcessing = false;
    214             return;
    215         }
    216         case Extent:
    217         {
    218             for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
    219                 if (m_atCharacter == m_queryStartPosition) {
    220                     unsigned int newOffset = textBox->start() + (it - start) + startOffset;
    221                     m_queryRectResult = textBox->calculateGlyphBoundaries(style, newOffset, *it);
    222                     m_stopProcessing = true;
    223                     return;
    224                 }
    225 
    226                 m_atCharacter++;
    227             }
    228 
    229             m_stopProcessing = false;
    230             return;
    231         }
    232         case Rotation:
    233         {
    234             for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
    235                 if (m_atCharacter == m_queryStartPosition) {
    236                     m_queryFloatResult = it->angle;
    237                     m_stopProcessing = true;
    238                     return;
    239                 }
    240 
    241                 m_atCharacter++;
    242             }
    243 
    244             m_stopProcessing = false;
    245             return;
    246         }
    247         case CharacterNumberAtPosition:
    248         {
    249             int offset = 0;
    250             SVGChar* charAtPos = textBox->closestCharacterToPosition(m_queryPointInput.x(), m_queryPointInput.y(), offset);
    251 
    252             offset += m_atCharacter;
    253             if (charAtPos && offset > m_queryLongResult)
    254                 m_queryLongResult = offset;
    255 
    256             m_atCharacter += end - start;
    257             m_stopProcessing = false;
    258             return;
    259         }
    260         default:
    261             ASSERT_NOT_REACHED();
    262             m_stopProcessing = true;
    263             return;
    264         }
    265     }
    266 
    267     void setQueryInputParameters(long startPosition, long length, FloatPoint referencePoint)
    268     {
    269         m_queryStartPosition = startPosition;
    270         m_queryLength = length;
    271         m_queryPointInput = referencePoint;
    272     }
    273 
    274     long longResult() const { return m_queryLongResult; }
    275     float floatResult() const { return m_queryFloatResult; }
    276     FloatPoint pointResult() const { return m_queryPointResult; }
    277     FloatRect rectResult() const { return m_queryRectResult; }
    278     bool stopProcessing() const { return m_stopProcessing; }
    279 
    280 private:
    281     const SVGTextContentElement* m_reference;
    282     QueryMode m_mode;
    283 
    284     long m_queryStartPosition;
    285     long m_queryLength;
    286     FloatPoint m_queryPointInput;
    287 
    288     long m_queryLongResult;
    289     float m_queryFloatResult;
    290     FloatPoint m_queryPointResult;
    291     FloatRect m_queryRectResult;
    292 
    293     bool m_stopProcessing;
    294     long m_atCharacter;
    295 };
    296 
    297 static Vector<SVGInlineTextBox*> findInlineTextBoxInTextChunks(const SVGTextContentElement* element, const Vector<SVGTextChunk>& chunks)
    298 {
    299     Vector<SVGTextChunk>::const_iterator it = chunks.begin();
    300     const Vector<SVGTextChunk>::const_iterator end = chunks.end();
    301 
    302     Vector<SVGInlineTextBox*> boxes;
    303 
    304     for (; it != end; ++it) {
    305         Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = it->boxes.begin();
    306         const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = it->boxes.end();
    307 
    308         for (; boxIt != boxEnd; ++boxIt) {
    309             SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(boxIt->box);
    310 
    311             Node* textElement = textBox->textRenderer()->parent()->node();
    312             ASSERT(textElement);
    313 
    314             if (textElement == element || textElement->parent() == element)
    315                 boxes.append(textBox);
    316         }
    317     }
    318 
    319     return boxes;
    320 }
    321 
    322 static inline SVGRootInlineBox* rootInlineBoxForTextContentElement(const SVGTextContentElement* element)
    323 {
    324     RenderObject* object = element->renderer();
    325 
    326     if (!object || !object->isSVGText() || object->isText())
    327         return 0;
    328 
    329     RenderBlock* svgText = toRenderBlock(object);
    330 
    331     // Find root inline box
    332     SVGRootInlineBox* rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox());
    333     if (!rootBox) {
    334         // Layout is not sync yet!
    335         element->document()->updateLayoutIgnorePendingStylesheets();
    336         rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox());
    337     }
    338 
    339     ASSERT(rootBox);
    340     return rootBox;
    341 }
    342 
    343 static inline SVGInlineTextBoxQueryWalker executeTextQuery(const SVGTextContentElement* element, SVGInlineTextBoxQueryWalker::QueryMode mode,
    344                                                            long startPosition = 0, long length = 0, FloatPoint referencePoint = FloatPoint())
    345 {
    346     SVGRootInlineBox* rootBox = rootInlineBoxForTextContentElement(element);
    347     if (!rootBox)
    348         return SVGInlineTextBoxQueryWalker(0, mode);
    349 
    350     // Find all inline text box associated with our renderer
    351     Vector<SVGInlineTextBox*> textBoxes = findInlineTextBoxInTextChunks(element, rootBox->svgTextChunks());
    352 
    353     // Walk text chunks to find chunks associated with our inline text box
    354     SVGInlineTextBoxQueryWalker walkerCallback(element, mode);
    355     walkerCallback.setQueryInputParameters(startPosition, length, referencePoint);
    356 
    357     SVGTextChunkWalker<SVGInlineTextBoxQueryWalker> walker(&walkerCallback, &SVGInlineTextBoxQueryWalker::chunkPortionCallback);
    358 
    359     Vector<SVGInlineTextBox*>::iterator it = textBoxes.begin();
    360     Vector<SVGInlineTextBox*>::iterator end = textBoxes.end();
    361 
    362     for (; it != end; ++it) {
    363         rootBox->walkTextChunks(&walker, *it);
    364 
    365         if (walkerCallback.stopProcessing())
    366             break;
    367     }
    368 
    369     return walkerCallback;
    370 }
    371 
    372 unsigned SVGTextContentElement::getNumberOfChars() const
    373 {
    374     document()->updateLayoutIgnorePendingStylesheets();
    375 
    376     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::NumberOfCharacters).longResult();
    377 }
    378 
    379 float SVGTextContentElement::getComputedTextLength() const
    380 {
    381     document()->updateLayoutIgnorePendingStylesheets();
    382 
    383     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::TextLength).floatResult();
    384 }
    385 
    386 float SVGTextContentElement::getSubStringLength(unsigned charnum, unsigned nchars, ExceptionCode& ec) const
    387 {
    388     document()->updateLayoutIgnorePendingStylesheets();
    389 
    390     unsigned numberOfChars = getNumberOfChars();
    391     if (charnum >= numberOfChars) {
    392         ec = INDEX_SIZE_ERR;
    393         return 0.0f;
    394     }
    395 
    396     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::SubStringLength, charnum, nchars).floatResult();
    397 }
    398 
    399 FloatPoint SVGTextContentElement::getStartPositionOfChar(unsigned charnum, ExceptionCode& ec) const
    400 {
    401     document()->updateLayoutIgnorePendingStylesheets();
    402 
    403     if (charnum > getNumberOfChars()) {
    404         ec = INDEX_SIZE_ERR;
    405         return FloatPoint();
    406     }
    407 
    408     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::StartPosition, charnum).pointResult();
    409 }
    410 
    411 FloatPoint SVGTextContentElement::getEndPositionOfChar(unsigned charnum, ExceptionCode& ec) const
    412 {
    413     document()->updateLayoutIgnorePendingStylesheets();
    414 
    415     if (charnum > getNumberOfChars()) {
    416         ec = INDEX_SIZE_ERR;
    417         return FloatPoint();
    418     }
    419 
    420     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::EndPosition, charnum).pointResult();
    421 }
    422 
    423 FloatRect SVGTextContentElement::getExtentOfChar(unsigned charnum, ExceptionCode& ec) const
    424 {
    425     document()->updateLayoutIgnorePendingStylesheets();
    426 
    427     if (charnum > getNumberOfChars()) {
    428         ec = INDEX_SIZE_ERR;
    429         return FloatRect();
    430     }
    431 
    432     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Extent, charnum).rectResult();
    433 }
    434 
    435 float SVGTextContentElement::getRotationOfChar(unsigned charnum, ExceptionCode& ec) const
    436 {
    437     document()->updateLayoutIgnorePendingStylesheets();
    438 
    439     if (charnum > getNumberOfChars()) {
    440         ec = INDEX_SIZE_ERR;
    441         return 0.0f;
    442     }
    443 
    444     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Rotation, charnum).floatResult();
    445 }
    446 
    447 int SVGTextContentElement::getCharNumAtPosition(const FloatPoint& point) const
    448 {
    449     document()->updateLayoutIgnorePendingStylesheets();
    450 
    451     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::CharacterNumberAtPosition, 0.0f, 0.0f, point).longResult();
    452 }
    453 
    454 void SVGTextContentElement::selectSubString(unsigned charnum, unsigned nchars, ExceptionCode& ec) const
    455 {
    456     unsigned numberOfChars = getNumberOfChars();
    457     if (charnum >= numberOfChars) {
    458         ec = INDEX_SIZE_ERR;
    459         return;
    460     }
    461 
    462     if (nchars > numberOfChars - charnum)
    463         nchars = numberOfChars - charnum;
    464 
    465     ASSERT(document());
    466     ASSERT(document()->frame());
    467 
    468     SelectionController* controller = document()->frame()->selection();
    469     if (!controller)
    470         return;
    471 
    472     // Find selection start
    473     VisiblePosition start(const_cast<SVGTextContentElement*>(this), 0, SEL_DEFAULT_AFFINITY);
    474     for (unsigned i = 0; i < charnum; ++i)
    475         start = start.next();
    476 
    477     // Find selection end
    478     VisiblePosition end(start);
    479     for (unsigned i = 0; i < nchars; ++i)
    480         end = end.next();
    481 
    482     controller->setSelection(VisibleSelection(start, end));
    483 }
    484 
    485 void SVGTextContentElement::parseMappedAttribute(MappedAttribute* attr)
    486 {
    487     if (attr->name() == SVGNames::lengthAdjustAttr) {
    488         if (attr->value() == "spacing")
    489             setLengthAdjustBaseValue(LENGTHADJUST_SPACING);
    490         else if (attr->value() == "spacingAndGlyphs")
    491             setLengthAdjustBaseValue(LENGTHADJUST_SPACINGANDGLYPHS);
    492     } else if (attr->name() == SVGNames::textLengthAttr) {
    493         setTextLengthBaseValue(SVGLength(LengthModeOther, attr->value()));
    494         if (textLengthBaseValue().value(this) < 0.0)
    495             document()->accessSVGExtensions()->reportError("A negative value for text attribute <textLength> is not allowed");
    496     } else {
    497         if (SVGTests::parseMappedAttribute(attr))
    498             return;
    499         if (SVGLangSpace::parseMappedAttribute(attr)) {
    500             if (attr->name().matches(XMLNames::spaceAttr)) {
    501                 DEFINE_STATIC_LOCAL(const AtomicString, preserveString, ("preserve"));
    502 
    503                 if (attr->value() == preserveString)
    504                     addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValuePre);
    505                 else
    506                     addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValueNowrap);
    507             }
    508             return;
    509         }
    510         if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
    511             return;
    512 
    513         SVGStyledElement::parseMappedAttribute(attr);
    514     }
    515 }
    516 
    517 void SVGTextContentElement::synchronizeProperty(const QualifiedName& attrName)
    518 {
    519     SVGStyledElement::synchronizeProperty(attrName);
    520 
    521     if (attrName == anyQName()) {
    522         synchronizeLengthAdjust();
    523         synchronizeTextLength();
    524         synchronizeExternalResourcesRequired();
    525         return;
    526     }
    527 
    528     if (attrName == SVGNames::lengthAdjustAttr)
    529         synchronizeLengthAdjust();
    530     else if (attrName == SVGNames::textLengthAttr)
    531         synchronizeTextLength();
    532     else if (SVGExternalResourcesRequired::isKnownAttribute(attrName))
    533         synchronizeExternalResourcesRequired();
    534 }
    535 
    536 bool SVGTextContentElement::isKnownAttribute(const QualifiedName& attrName)
    537 {
    538     return (attrName.matches(SVGNames::lengthAdjustAttr) ||
    539             attrName.matches(SVGNames::textLengthAttr) ||
    540             SVGTests::isKnownAttribute(attrName) ||
    541             SVGLangSpace::isKnownAttribute(attrName) ||
    542             SVGExternalResourcesRequired::isKnownAttribute(attrName) ||
    543             SVGStyledElement::isKnownAttribute(attrName));
    544 }
    545 
    546 }
    547 
    548 #endif // ENABLE(SVG)
    549