File indexing completed on 2024-04-28 11:39:22

0001 /*
0002     Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
0003                   2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org>
0004 
0005     This library is free software; you can redistribute it and/or
0006     modify it under the terms of the GNU Library General Public
0007     License as published by the Free Software Foundation; either
0008     version 2 of the License, or (at your option) any later version.
0009 
0010     This library is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013     Library General Public License for more details.
0014 
0015     You should have received a copy of the GNU Library General Public License
0016     along with this library; see the file COPYING.LIB.  If not, write to
0017     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018     Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "wtf/Platform.h"
0022 
0023 #if ENABLE(SVG)
0024 #include "SVGTextContentElement.h"
0025 
0026 #include "cssvalues.h"
0027 
0028 /*#include "CSSPropertyNames.h"
0029 #include "CSSValueKeywords.h"*/
0030 #include "ExceptionCode.h"
0031 #include "FloatPoint.h"
0032 #include "FloatRect.h"
0033 /*#include "Frame.h"
0034 #include "Position.h"*/
0035 #include "RenderSVGText.h"
0036 /*#include "SelectionController.h"*/
0037 #include "SVGCharacterLayoutInfo.h"
0038 #include "SVGRootInlineBox.h"
0039 #include "SVGLength.h"
0040 #include "SVGInlineTextBox.h"
0041 #include "SVGNames.h"
0042 
0043 namespace WebCore
0044 {
0045 
0046 SVGTextContentElement::SVGTextContentElement(const QualifiedName &tagName, Document *doc)
0047     : SVGStyledElement(tagName, doc)
0048     , SVGTests()
0049     , SVGLangSpace()
0050     , SVGExternalResourcesRequired()
0051     , m_textLength(this, LengthModeOther)
0052     , m_lengthAdjust(LENGTHADJUST_SPACING)
0053 {
0054 }
0055 
0056 SVGTextContentElement::~SVGTextContentElement()
0057 {
0058 }
0059 
0060 ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, SVGLength, Length, length, TextLength, textLength, SVGNames::textLengthAttr, m_textLength)
0061 ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, int, Enumeration, enumeration, LengthAdjust, lengthAdjust, SVGNames::lengthAdjustAttr, m_lengthAdjust)
0062 
0063 static inline float cumulativeCharacterRangeLength(const Vector<SVGChar>::iterator &start, const Vector<SVGChar>::iterator &end, SVGInlineTextBox *textBox,
0064         int startOffset, long startPosition, long length, bool isVerticalText, long &atCharacter)
0065 {
0066     if (!length) {
0067         return 0.0f;
0068     }
0069 
0070     float textLength = 0.0f;
0071     RenderStyle *style = textBox->renderText()->style();
0072 
0073     bool usesFullRange = (startPosition == -1 && length == -1);
0074 
0075     for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
0076         if (usesFullRange || (atCharacter >= startPosition && atCharacter <= startPosition + length)) {
0077             unsigned int newOffset = textBox->start() + (it - start) + startOffset;
0078 
0079             // Take RTL text into account and pick right glyph width/height.
0080             /*FIXME khtml if (textBox->direction() == RTL)
0081                 newOffset = textBox->start() + textBox->end() - newOffset;*/
0082 
0083             // FIXME: does this handle multichar glyphs ok? not sure
0084             int charsConsumed = 0;
0085             String glyphName;
0086             if (isVerticalText) {
0087                 textLength += textBox->calculateGlyphHeight(style, newOffset, 0);
0088             } else {
0089                 textLength += textBox->calculateGlyphWidth(style, newOffset, 0, charsConsumed, glyphName);
0090             }
0091         }
0092 
0093         if (!usesFullRange) {
0094             if (atCharacter == startPosition + length - 1) {
0095                 break;
0096             }
0097 
0098             atCharacter++;
0099         }
0100     }
0101 
0102     return textLength;
0103 }
0104 
0105 // Helper class for querying certain glyph information
0106 struct SVGInlineTextBoxQueryWalker {
0107     typedef enum {
0108         NumberOfCharacters,
0109         TextLength,
0110         SubStringLength,
0111         StartPosition,
0112         EndPosition,
0113         Extent,
0114         Rotation,
0115         CharacterNumberAtPosition
0116     } QueryMode;
0117 
0118     SVGInlineTextBoxQueryWalker(const SVGTextContentElement *reference, QueryMode mode)
0119         : m_reference(reference)
0120         , m_mode(mode)
0121         , m_queryStartPosition(0)
0122         , m_queryLength(0)
0123         , m_queryPointInput()
0124         , m_queryLongResult(0)
0125         , m_queryFloatResult(0.0f)
0126         , m_queryPointResult()
0127         , m_queryRectResult()
0128         , m_stopProcessing(true)
0129         , m_atCharacter(0)
0130     {
0131     }
0132 
0133     void chunkPortionCallback(SVGInlineTextBox *textBox, int startOffset, const AffineTransform &chunkCtm,
0134                               const Vector<SVGChar>::iterator &start, const Vector<SVGChar>::iterator &end)
0135     {
0136         Q_UNUSED(chunkCtm);
0137         RenderStyle *style = textBox->renderText()->style();
0138         bool isVerticalText = style->svgStyle()->writingMode() == WM_TBRL || style->svgStyle()->writingMode() == WM_TB;
0139 
0140         switch (m_mode) {
0141         case NumberOfCharacters: {
0142             m_queryLongResult += (end - start);
0143             m_stopProcessing = false;
0144             return;
0145         }
0146         case TextLength: {
0147             float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, -1, -1, isVerticalText, m_atCharacter);
0148 
0149             if (isVerticalText) {
0150                 m_queryFloatResult += textLength;
0151             } else {
0152                 m_queryFloatResult += textLength;
0153             }
0154 
0155             m_stopProcessing = false;
0156             return;
0157         }
0158         case SubStringLength: {
0159             long startPosition = m_queryStartPosition;
0160             long length = m_queryLength;
0161 
0162             float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, startPosition, length, isVerticalText, m_atCharacter);
0163 
0164             if (isVerticalText) {
0165                 m_queryFloatResult += textLength;
0166             } else {
0167                 m_queryFloatResult += textLength;
0168             }
0169 
0170             if (m_atCharacter == startPosition + length) {
0171                 m_stopProcessing = true;
0172             } else {
0173                 m_stopProcessing = false;
0174             }
0175 
0176             return;
0177         }
0178         case StartPosition: {
0179             for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
0180                 if (m_atCharacter == m_queryStartPosition) {
0181                     m_queryPointResult = FloatPoint(it->x, it->y);
0182                     m_stopProcessing = true;
0183                     return;
0184                 }
0185 
0186                 m_atCharacter++;
0187             }
0188 
0189             m_stopProcessing = false;
0190             return;
0191         }
0192         case EndPosition: {
0193             for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
0194                 if (m_atCharacter == m_queryStartPosition) {
0195                     unsigned int newOffset = textBox->start() + (it - start) + startOffset;
0196 
0197                     // Take RTL text into account and pick right glyph width/height.
0198                     /*FIXME khtml if (textBox->direction() == RTL)
0199                         newOffset = textBox->start() + textBox->end() - newOffset;*/
0200 
0201                     int charsConsumed;
0202                     String glyphName;
0203                     if (isVerticalText) {
0204                         m_queryPointResult.move(it->x, it->y + textBox->calculateGlyphHeight(style, newOffset, end - it));
0205                     } else {
0206                         m_queryPointResult.move(it->x + textBox->calculateGlyphWidth(style, newOffset, end - it, charsConsumed, glyphName), it->y);
0207                     }
0208 
0209                     m_stopProcessing = true;
0210                     return;
0211                 }
0212 
0213                 m_atCharacter++;
0214             }
0215 
0216             m_stopProcessing = false;
0217             return;
0218         }
0219         case Extent: {
0220             for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
0221                 if (m_atCharacter == m_queryStartPosition) {
0222                     unsigned int newOffset = textBox->start() + (it - start) + startOffset;
0223                     m_queryRectResult = textBox->calculateGlyphBoundaries(style, newOffset, *it);
0224                     m_stopProcessing = true;
0225                     return;
0226                 }
0227 
0228                 m_atCharacter++;
0229             }
0230 
0231             m_stopProcessing = false;
0232             return;
0233         }
0234         case Rotation: {
0235             for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
0236                 if (m_atCharacter == m_queryStartPosition) {
0237                     m_queryFloatResult = it->angle;
0238                     m_stopProcessing = true;
0239                     return;
0240                 }
0241 
0242                 m_atCharacter++;
0243             }
0244 
0245             m_stopProcessing = false;
0246             return;
0247         }
0248         case CharacterNumberAtPosition: {
0249             int offset = 0;
0250             SVGChar *charAtPos = textBox->closestCharacterToPosition(m_queryPointInput.x(), m_queryPointInput.y(), offset);
0251 
0252             offset += m_atCharacter;
0253             if (charAtPos && offset > m_queryLongResult) {
0254                 m_queryLongResult = offset;
0255             }
0256 
0257             m_atCharacter += end - start;
0258             m_stopProcessing = false;
0259             return;
0260         }
0261         default:
0262             ASSERT_NOT_REACHED();
0263             m_stopProcessing = true;
0264             return;
0265         }
0266     }
0267 
0268     void setQueryInputParameters(long startPosition, long length, FloatPoint referencePoint)
0269     {
0270         m_queryStartPosition = startPosition;
0271         m_queryLength = length;
0272         m_queryPointInput = referencePoint;
0273     }
0274 
0275     long longResult() const
0276     {
0277         return m_queryLongResult;
0278     }
0279     float floatResult() const
0280     {
0281         return m_queryFloatResult;
0282     }
0283     FloatPoint pointResult() const
0284     {
0285         return m_queryPointResult;
0286     }
0287     FloatRect rectResult() const
0288     {
0289         return m_queryRectResult;
0290     }
0291     bool stopProcessing() const
0292     {
0293         return m_stopProcessing;
0294     }
0295 
0296 private:
0297     const SVGTextContentElement *m_reference;
0298     QueryMode m_mode;
0299 
0300     long m_queryStartPosition;
0301     long m_queryLength;
0302     FloatPoint m_queryPointInput;
0303 
0304     long m_queryLongResult;
0305     float m_queryFloatResult;
0306     FloatPoint m_queryPointResult;
0307     FloatRect m_queryRectResult;
0308 
0309     bool m_stopProcessing;
0310     long m_atCharacter;
0311 };
0312 
0313 static Vector<SVGInlineTextBox *> findInlineTextBoxInTextChunks(const SVGTextContentElement *element, const Vector<SVGTextChunk> &chunks)
0314 {
0315     Vector<SVGTextChunk>::const_iterator it = chunks.begin();
0316     const Vector<SVGTextChunk>::const_iterator end = chunks.end();
0317 
0318     Vector<SVGInlineTextBox *> boxes;
0319 
0320     for (; it != end; ++it) {
0321         Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = it->boxes.begin();
0322         const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = it->boxes.end();
0323 
0324         for (; boxIt != boxEnd; ++boxIt) {
0325             SVGInlineTextBox *textBox = static_cast<SVGInlineTextBox *>(boxIt->box);
0326 
0327             Node *textElement = textBox->renderText()->parent()->element();
0328             ASSERT(textElement);
0329 
0330             if (textElement == element || textElement->parent() == element) {
0331                 boxes.append(textBox);
0332             }
0333         }
0334     }
0335 
0336     return boxes;
0337 }
0338 
0339 static inline SVGRootInlineBox *rootInlineBoxForTextContentElement(const SVGTextContentElement *element)
0340 {
0341     RenderObject *object = element->renderer();
0342 
0343     if (!object || !object->isSVGText() || object->isText()) {
0344         return nullptr;
0345     }
0346 
0347     RenderSVGText *svgText = static_cast<RenderSVGText *>(object);
0348 
0349     // Find root inline box
0350     SVGRootInlineBox *rootBox = static_cast<SVGRootInlineBox *>(svgText->firstRootBox());
0351     if (!rootBox) {
0352         // Layout is not sync yet!
0353         /*FIXME khtml element->document()->updateLayoutIgnorePendingStylesheets();*/
0354         rootBox = static_cast<SVGRootInlineBox *>(svgText->firstRootBox());
0355     }
0356 
0357     ASSERT(rootBox);
0358     return rootBox;
0359 }
0360 
0361 static inline SVGInlineTextBoxQueryWalker executeTextQuery(const SVGTextContentElement *element, SVGInlineTextBoxQueryWalker::QueryMode mode,
0362         long startPosition = 0, long length = 0, FloatPoint referencePoint = FloatPoint())
0363 {
0364     SVGRootInlineBox *rootBox = rootInlineBoxForTextContentElement(element);
0365     if (!rootBox) {
0366         return SVGInlineTextBoxQueryWalker(nullptr, mode);
0367     }
0368 
0369     // Find all inline text box associated with our renderer
0370     Vector<SVGInlineTextBox *> textBoxes = findInlineTextBoxInTextChunks(element, rootBox->svgTextChunks());
0371 
0372     // Walk text chunks to find chunks associated with our inline text box
0373     SVGInlineTextBoxQueryWalker walkerCallback(element, mode);
0374     walkerCallback.setQueryInputParameters(startPosition, length, referencePoint);
0375 
0376     SVGTextChunkWalker<SVGInlineTextBoxQueryWalker> walker(&walkerCallback, &SVGInlineTextBoxQueryWalker::chunkPortionCallback);
0377 
0378     Vector<SVGInlineTextBox *>::iterator it = textBoxes.begin();
0379     Vector<SVGInlineTextBox *>::iterator end = textBoxes.end();
0380 
0381     for (; it != end; ++it) {
0382         rootBox->walkTextChunks(&walker, *it);
0383 
0384         if (walkerCallback.stopProcessing()) {
0385             break;
0386         }
0387     }
0388 
0389     return walkerCallback;
0390 }
0391 
0392 long SVGTextContentElement::getNumberOfChars() const
0393 {
0394     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::NumberOfCharacters).longResult();
0395 }
0396 
0397 float SVGTextContentElement::getComputedTextLength() const
0398 {
0399     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::TextLength).floatResult();
0400 }
0401 
0402 float SVGTextContentElement::getSubStringLength(long charnum, long nchars, ExceptionCode &ec) const
0403 {
0404     // Differences to SVG 1.1 spec, as the spec is clearly wrong. TODO: Raise SVG WG issue!
0405     // #1: We accept a 'long nchars' parameter instead of 'unsigned long nchars' to be able
0406     //     to catch cases where someone called us with a negative 'nchars' value - in those
0407     //     cases we'll just throw a 'INDEX_SIZE_ERR' (acid3 implicitly agrees with us)
0408     //
0409     // #2: We only throw if 'charnum + nchars' is greater than the number of characters, not
0410     //     if it's equal, as this really doesn't make any sense (no way to measure the last character!)
0411     //
0412     // #3: If 'charnum' is greater than or equal to 'numberOfChars', we're throwing an exception here
0413     //     as the result is undefined for every other value of 'nchars' than '0'.
0414 
0415     long numberOfChars = getNumberOfChars();
0416     if (charnum < 0 || nchars < 0 || numberOfChars <= charnum || charnum + nchars > numberOfChars) {
0417         ec = DOMException::INDEX_SIZE_ERR;
0418         return 0.0f;
0419     }
0420 
0421     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::SubStringLength, charnum, nchars).floatResult();
0422 }
0423 
0424 FloatPoint SVGTextContentElement::getStartPositionOfChar(long charnum, ExceptionCode &ec) const
0425 {
0426     if (charnum < 0 || charnum > getNumberOfChars()) {
0427         ec = DOMException::INDEX_SIZE_ERR;
0428         return FloatPoint();
0429     }
0430 
0431     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::StartPosition, charnum).pointResult();
0432 }
0433 
0434 FloatPoint SVGTextContentElement::getEndPositionOfChar(long charnum, ExceptionCode &ec) const
0435 {
0436     if (charnum < 0 || charnum > getNumberOfChars()) {
0437         ec = DOMException::INDEX_SIZE_ERR;
0438         return FloatPoint();
0439     }
0440 
0441     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::EndPosition, charnum).pointResult();
0442 }
0443 
0444 FloatRect SVGTextContentElement::getExtentOfChar(long charnum, ExceptionCode &ec) const
0445 {
0446     if (charnum < 0 || charnum > getNumberOfChars()) {
0447         ec = DOMException::INDEX_SIZE_ERR;
0448         return FloatRect();
0449     }
0450 
0451     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Extent, charnum).rectResult();
0452 }
0453 
0454 float SVGTextContentElement::getRotationOfChar(long charnum, ExceptionCode &ec) const
0455 {
0456     if (charnum < 0 || charnum > getNumberOfChars()) {
0457         ec = DOMException::INDEX_SIZE_ERR;
0458         return 0.0f;
0459     }
0460 
0461     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Rotation, charnum).floatResult();
0462 }
0463 
0464 long SVGTextContentElement::getCharNumAtPosition(const FloatPoint &point) const
0465 {
0466     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::CharacterNumberAtPosition, 0.0f, 0.0f, point).longResult();
0467 }
0468 
0469 void SVGTextContentElement::selectSubString(long charnum, long nchars, ExceptionCode &ec) const
0470 {
0471     long numberOfChars = getNumberOfChars();
0472     if (charnum < 0 || nchars < 0 || charnum > numberOfChars) {
0473         ec = DOMException::INDEX_SIZE_ERR;
0474         return;
0475     }
0476 
0477     if (nchars > numberOfChars - charnum) {
0478         nchars = numberOfChars - charnum;
0479     }
0480 
0481     ASSERT(document());
0482     //khtml ASSERT(document()->frame());
0483 
0484     /*FIXME SelectionController* controller = document()->frame()->selectionController();
0485     if (!controller)
0486         return;
0487 
0488     // Find selection start
0489     VisiblePosition start(const_cast<SVGTextContentElement*>(this), 0, SEL_DEFAULT_AFFINITY);
0490     for (long i = 0; i < charnum; ++i)
0491         start = start.next();
0492 
0493     // Find selection end
0494     VisiblePosition end(start);
0495     for (long i = 0; i < nchars; ++i)
0496         end = end.next();
0497 
0498     controller->setSelection(Selection(start, end));*/
0499 }
0500 
0501 void SVGTextContentElement::parseMappedAttribute(MappedAttribute *attr)
0502 {
0503     if (attr->name() == SVGNames::lengthAdjustAttr) {
0504         if (attr->value() == "spacing") {
0505             setLengthAdjustBaseValue(LENGTHADJUST_SPACING);
0506         } else if (attr->value() == "spacingAndGlyphs") {
0507             setLengthAdjustBaseValue(LENGTHADJUST_SPACINGANDGLYPHS);
0508         }
0509     } else if (attr->name() == SVGNames::textLengthAttr) {
0510         setTextLengthBaseValue(SVGLength(this, LengthModeOther, attr->value()));
0511         if (textLength().value() < 0.0) {
0512             document()->accessSVGExtensions()->reportError("A negative value for text attribute <textLength> is not allowed");
0513         }
0514     } else {
0515         if (SVGTests::parseMappedAttribute(attr)) {
0516             return;
0517         }
0518         if (SVGLangSpace::parseMappedAttribute(attr)) {
0519             if (attr->id() == ATTR_XML_SPACE) {
0520                 static const DOMString preserveString("preserve");
0521 
0522                 if (attr->value() == preserveString) {
0523                     addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_PRE);
0524                 } else {
0525                     addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_NOWRAP);
0526                 }
0527             }
0528             return;
0529         }
0530         if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) {
0531             return;
0532         }
0533 
0534         SVGStyledElement::parseMappedAttribute(attr);
0535     }
0536 }
0537 
0538 bool SVGTextContentElement::isKnownAttribute(const QualifiedName &attrName)
0539 {
0540     return (attrName.matches(SVGNames::lengthAdjustAttr) ||
0541             attrName.matches(SVGNames::textLengthAttr) ||
0542             SVGTests::isKnownAttribute(attrName) ||
0543             SVGLangSpace::isKnownAttribute(attrName) ||
0544             SVGExternalResourcesRequired::isKnownAttribute(attrName) ||
0545             SVGStyledElement::isKnownAttribute(attrName));
0546 }
0547 
0548 }
0549 
0550 #endif // ENABLE(SVG)