summaryrefslogtreecommitdiffstats
path: root/WebCore/svg/SVGTextContentElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/svg/SVGTextContentElement.cpp')
-rw-r--r--WebCore/svg/SVGTextContentElement.cpp496
1 files changed, 496 insertions, 0 deletions
diff --git a/WebCore/svg/SVGTextContentElement.cpp b/WebCore/svg/SVGTextContentElement.cpp
new file mode 100644
index 0000000..03ed592
--- /dev/null
+++ b/WebCore/svg/SVGTextContentElement.cpp
@@ -0,0 +1,496 @@
+/*
+ Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
+
+ This file is part of the KDE project
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+
+#if ENABLE(SVG)
+#include "SVGTextContentElement.h"
+
+#include "CSSPropertyNames.h"
+#include "CSSValueKeywords.h"
+#include "ExceptionCode.h"
+#include "FloatPoint.h"
+#include "FloatRect.h"
+#include "Frame.h"
+#include "Position.h"
+#include "RenderSVGText.h"
+#include "SelectionController.h"
+#include "SVGCharacterLayoutInfo.h"
+#include "SVGRootInlineBox.h"
+#include "SVGLength.h"
+#include "SVGInlineTextBox.h"
+#include "SVGNames.h"
+#include "XMLNames.h"
+
+namespace WebCore {
+
+SVGTextContentElement::SVGTextContentElement(const QualifiedName& tagName, Document* doc)
+ : SVGStyledElement(tagName, doc)
+ , SVGTests()
+ , SVGLangSpace()
+ , SVGExternalResourcesRequired()
+ , m_textLength(this, LengthModeOther)
+ , m_lengthAdjust(LENGTHADJUST_SPACING)
+{
+}
+
+SVGTextContentElement::~SVGTextContentElement()
+{
+}
+
+ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, SVGLength, Length, length, TextLength, textLength, SVGNames::textLengthAttr, m_textLength)
+ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, int, Enumeration, enumeration, LengthAdjust, lengthAdjust, SVGNames::lengthAdjustAttr, m_lengthAdjust)
+
+static inline float cummulatedCharacterRangeLength(const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end, SVGInlineTextBox* textBox,
+ int startOffset, long startPosition, long length, bool isVerticalText, long& atCharacter)
+{
+ float textLength = 0.0f;
+ RenderStyle* style = textBox->textObject()->style();
+
+ bool usesFullRange = (startPosition == -1 && length == -1);
+
+ for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
+ if (usesFullRange || (atCharacter >= startPosition && atCharacter <= startPosition + length)) {
+ unsigned int newOffset = textBox->start() + (it - start) + startOffset;
+
+ // Take RTL text into account and pick right glyph width/height.
+ if (textBox->m_reversed)
+ newOffset = textBox->start() + textBox->end() - newOffset;
+
+ if (isVerticalText)
+ textLength += textBox->calculateGlyphHeight(style, newOffset);
+ else
+ textLength += textBox->calculateGlyphWidth(style, newOffset);
+ }
+
+ if (!usesFullRange) {
+ if (atCharacter < startPosition + length)
+ atCharacter++;
+ else if (atCharacter == startPosition + length)
+ break;
+ }
+ }
+
+ return textLength;
+}
+
+// Helper class for querying certain glyph information
+struct SVGInlineTextBoxQueryWalker {
+ typedef enum {
+ NumberOfCharacters,
+ TextLength,
+ SubStringLength,
+ StartPosition,
+ EndPosition,
+ Extent,
+ Rotation,
+ CharacterNumberAtPosition
+ } QueryMode;
+
+ SVGInlineTextBoxQueryWalker(const SVGTextContentElement* reference, QueryMode mode)
+ : m_reference(reference)
+ , m_mode(mode)
+ , m_queryStartPosition(0)
+ , m_queryLength(0)
+ , m_queryPointInput()
+ , m_queryLongResult(0)
+ , m_queryFloatResult(0.0f)
+ , m_queryPointResult()
+ , m_queryRectResult()
+ , m_stopProcessing(true)
+ , m_atCharacter(0)
+ {
+ }
+
+ void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const AffineTransform& chunkCtm,
+ const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end)
+ {
+ RenderStyle* style = textBox->textObject()->style();
+ bool isVerticalText = style->svgStyle()->writingMode() == WM_TBRL || style->svgStyle()->writingMode() == WM_TB;
+
+ switch (m_mode) {
+ case NumberOfCharacters:
+ {
+ m_queryLongResult += (end - start);
+ m_stopProcessing = false;
+ return;
+ }
+ case TextLength:
+ {
+ float textLength = cummulatedCharacterRangeLength(start, end, textBox, startOffset, -1, -1, isVerticalText, m_atCharacter);
+
+ if (isVerticalText)
+ m_queryFloatResult += textLength;
+ else
+ m_queryFloatResult += textLength;
+
+ m_stopProcessing = false;
+ return;
+ }
+ case SubStringLength:
+ {
+ long startPosition = m_queryStartPosition;
+ long length = m_queryLength;
+
+ float textLength = cummulatedCharacterRangeLength(start, end, textBox, startOffset, startPosition, length, isVerticalText, m_atCharacter);
+
+ if (isVerticalText)
+ m_queryFloatResult += textLength;
+ else
+ m_queryFloatResult += textLength;
+
+ if (m_atCharacter == startPosition + length)
+ m_stopProcessing = true;
+ else
+ m_stopProcessing = false;
+
+ return;
+ }
+ case StartPosition:
+ {
+ for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
+ if (m_atCharacter == m_queryStartPosition) {
+ m_queryPointResult = FloatPoint(it->x, it->y);
+ m_stopProcessing = true;
+ return;
+ }
+
+ m_atCharacter++;
+ }
+
+ m_stopProcessing = false;
+ return;
+ }
+ case EndPosition:
+ {
+ for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
+ if (m_atCharacter == m_queryStartPosition) {
+ unsigned int newOffset = textBox->start() + (it - start) + startOffset;
+
+ // Take RTL text into account and pick right glyph width/height.
+ if (textBox->m_reversed)
+ newOffset = textBox->start() + textBox->end() - newOffset;
+
+ if (isVerticalText)
+ m_queryPointResult.move(it->x, it->y + textBox->calculateGlyphHeight(style, newOffset));
+ else
+ m_queryPointResult.move(it->x + textBox->calculateGlyphWidth(style, newOffset), it->y);
+
+ m_stopProcessing = true;
+ return;
+ }
+
+ m_atCharacter++;
+ }
+
+ m_stopProcessing = false;
+ return;
+ }
+ case Extent:
+ {
+ for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
+ if (m_atCharacter == m_queryStartPosition) {
+ unsigned int newOffset = textBox->start() + (it - start) + startOffset;
+ m_queryRectResult = textBox->calculateGlyphBoundaries(style, newOffset, *it);
+ m_stopProcessing = true;
+ return;
+ }
+
+ m_atCharacter++;
+ }
+
+ m_stopProcessing = false;
+ return;
+ }
+ case Rotation:
+ {
+ for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
+ if (m_atCharacter == m_queryStartPosition) {
+ m_queryFloatResult = it->angle;
+ m_stopProcessing = true;
+ return;
+ }
+
+ m_atCharacter++;
+ }
+
+ m_stopProcessing = false;
+ return;
+ }
+ case CharacterNumberAtPosition:
+ {
+ int offset = 0;
+ SVGChar* charAtPos = textBox->closestCharacterToPosition(m_queryPointInput.x(), m_queryPointInput.y(), offset);
+
+ offset += m_atCharacter;
+ if (charAtPos && offset > m_queryLongResult)
+ m_queryLongResult = offset;
+
+ m_atCharacter += end - start;
+ m_stopProcessing = false;
+ return;
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ m_stopProcessing = true;
+ return;
+ }
+ }
+
+ void setQueryInputParameters(long startPosition, long length, FloatPoint referencePoint)
+ {
+ m_queryStartPosition = startPosition;
+ m_queryLength = length;
+ m_queryPointInput = referencePoint;
+ }
+
+ long longResult() const { return m_queryLongResult; }
+ float floatResult() const { return m_queryFloatResult; }
+ FloatPoint pointResult() const { return m_queryPointResult; }
+ FloatRect rectResult() const { return m_queryRectResult; }
+ bool stopProcessing() const { return m_stopProcessing; }
+
+private:
+ const SVGTextContentElement* m_reference;
+ QueryMode m_mode;
+
+ long m_queryStartPosition;
+ long m_queryLength;
+ FloatPoint m_queryPointInput;
+
+ long m_queryLongResult;
+ float m_queryFloatResult;
+ FloatPoint m_queryPointResult;
+ FloatRect m_queryRectResult;
+
+ bool m_stopProcessing;
+ long m_atCharacter;
+};
+
+static Vector<SVGInlineTextBox*> findInlineTextBoxInTextChunks(const SVGTextContentElement* element, const Vector<SVGTextChunk>& chunks)
+{
+ Vector<SVGTextChunk>::const_iterator it = chunks.begin();
+ const Vector<SVGTextChunk>::const_iterator end = chunks.end();
+
+ Vector<SVGInlineTextBox*> boxes;
+
+ for (; it != end; ++it) {
+ Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = it->boxes.begin();
+ const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = it->boxes.end();
+
+ for (; boxIt != boxEnd; ++boxIt) {
+ SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(boxIt->box);
+
+ Node* textElement = textBox->textObject()->parent()->element();
+ ASSERT(textElement);
+
+ if (textElement == element || textElement->parent() == element)
+ boxes.append(textBox);
+ }
+ }
+
+ return boxes;
+}
+
+static inline SVGRootInlineBox* rootInlineBoxForTextContentElement(const SVGTextContentElement* element)
+{
+ RenderObject* object = element->renderer();
+
+ if (!object || !object->isSVGText() || object->isText())
+ return 0;
+
+ RenderSVGText* svgText = static_cast<RenderSVGText*>(object);
+
+ // Find root inline box
+ SVGRootInlineBox* rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox());
+ if (!rootBox) {
+ // Layout is not sync yet!
+ element->document()->updateLayoutIgnorePendingStylesheets();
+ rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox());
+ }
+
+ ASSERT(rootBox);
+ return rootBox;
+}
+
+static inline SVGInlineTextBoxQueryWalker executeTextQuery(const SVGTextContentElement* element, SVGInlineTextBoxQueryWalker::QueryMode mode,
+ long startPosition = 0, long length = 0, FloatPoint referencePoint = FloatPoint())
+{
+ SVGRootInlineBox* rootBox = rootInlineBoxForTextContentElement(element);
+ if (!rootBox)
+ return SVGInlineTextBoxQueryWalker(0, mode);
+
+ // Find all inline text box associated with our renderer
+ Vector<SVGInlineTextBox*> textBoxes = findInlineTextBoxInTextChunks(element, rootBox->svgTextChunks());
+
+ // Walk text chunks to find chunks associated with our inline text box
+ SVGInlineTextBoxQueryWalker walkerCallback(element, mode);
+ walkerCallback.setQueryInputParameters(startPosition, length, referencePoint);
+
+ SVGTextChunkWalker<SVGInlineTextBoxQueryWalker> walker(&walkerCallback, &SVGInlineTextBoxQueryWalker::chunkPortionCallback);
+
+ Vector<SVGInlineTextBox*>::iterator it = textBoxes.begin();
+ Vector<SVGInlineTextBox*>::iterator end = textBoxes.end();
+
+ for (; it != end; ++it) {
+ rootBox->walkTextChunks(&walker, *it);
+
+ if (walkerCallback.stopProcessing())
+ break;
+ }
+
+ return walkerCallback;
+}
+
+long SVGTextContentElement::getNumberOfChars() const
+{
+ return executeTextQuery(this, SVGInlineTextBoxQueryWalker::NumberOfCharacters).longResult();
+}
+
+float SVGTextContentElement::getComputedTextLength() const
+{
+ return executeTextQuery(this, SVGInlineTextBoxQueryWalker::TextLength).floatResult();
+}
+
+float SVGTextContentElement::getSubStringLength(long charnum, unsigned long nchars, ExceptionCode& ec) const
+{
+ if (charnum < 0 || charnum > getNumberOfChars()) {
+ ec = INDEX_SIZE_ERR;
+ return 0.0f;
+ }
+
+ return executeTextQuery(this, SVGInlineTextBoxQueryWalker::SubStringLength, charnum, nchars).floatResult();
+}
+
+FloatPoint SVGTextContentElement::getStartPositionOfChar(long charnum, ExceptionCode& ec) const
+{
+ if (charnum < 0 || charnum > getNumberOfChars()) {
+ ec = INDEX_SIZE_ERR;
+ return FloatPoint();
+ }
+
+ return executeTextQuery(this, SVGInlineTextBoxQueryWalker::StartPosition, charnum).pointResult();
+}
+
+FloatPoint SVGTextContentElement::getEndPositionOfChar(long charnum, ExceptionCode& ec) const
+{
+ if (charnum < 0 || charnum > getNumberOfChars()) {
+ ec = INDEX_SIZE_ERR;
+ return FloatPoint();
+ }
+
+ return executeTextQuery(this, SVGInlineTextBoxQueryWalker::EndPosition, charnum).pointResult();
+}
+
+FloatRect SVGTextContentElement::getExtentOfChar(long charnum, ExceptionCode& ec) const
+{
+ if (charnum < 0 || charnum > getNumberOfChars()) {
+ ec = INDEX_SIZE_ERR;
+ return FloatRect();
+ }
+
+ return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Extent, charnum).rectResult();
+}
+
+float SVGTextContentElement::getRotationOfChar(long charnum, ExceptionCode& ec) const
+{
+ if (charnum < 0 || charnum > getNumberOfChars()) {
+ ec = INDEX_SIZE_ERR;
+ return 0.0f;
+ }
+
+ return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Rotation, charnum).floatResult();
+}
+
+long SVGTextContentElement::getCharNumAtPosition(const FloatPoint& point) const
+{
+ return executeTextQuery(this, SVGInlineTextBoxQueryWalker::CharacterNumberAtPosition, 0.0f, 0.0f, point).longResult();
+}
+
+void SVGTextContentElement::selectSubString(long charnum, long nchars, ExceptionCode& ec) const
+{
+ long numberOfChars = getNumberOfChars();
+ if (charnum < 0 || nchars < 0 || charnum > numberOfChars) {
+ ec = INDEX_SIZE_ERR;
+ return;
+ }
+
+ if (nchars > numberOfChars - charnum)
+ nchars = numberOfChars - charnum;
+
+ ASSERT(document());
+ ASSERT(document()->frame());
+
+ SelectionController* controller = document()->frame()->selectionController();
+ if (!controller)
+ return;
+
+ // Find selection start
+ VisiblePosition start(const_cast<SVGTextContentElement*>(this), 0, SEL_DEFAULT_AFFINITY);
+ for (long i = 0; i < charnum; ++i)
+ start = start.next();
+
+ // Find selection end
+ VisiblePosition end(start);
+ for (long i = 0; i < nchars; ++i)
+ end = end.next();
+
+ controller->setSelection(Selection(start, end));
+}
+
+void SVGTextContentElement::parseMappedAttribute(MappedAttribute* attr)
+{
+ if (attr->name() == SVGNames::lengthAdjustAttr) {
+ if (attr->value() == "spacing")
+ setLengthAdjustBaseValue(LENGTHADJUST_SPACING);
+ else if (attr->value() == "spacingAndGlyphs")
+ setLengthAdjustBaseValue(LENGTHADJUST_SPACINGANDGLYPHS);
+ } else if (attr->name() == SVGNames::textLengthAttr) {
+ setTextLengthBaseValue(SVGLength(this, LengthModeOther, attr->value()));
+ if (textLength().value() < 0.0)
+ document()->accessSVGExtensions()->reportError("A negative value for text attribute <textLength> is not allowed");
+ } else {
+ if (SVGTests::parseMappedAttribute(attr))
+ return;
+ if (SVGLangSpace::parseMappedAttribute(attr)) {
+ if (attr->name().matches(XMLNames::spaceAttr)) {
+ static const AtomicString preserveString("preserve");
+
+ if (attr->value() == preserveString)
+ addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_PRE);
+ else
+ addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_NOWRAP);
+ }
+ return;
+ }
+ if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
+ return;
+
+ SVGStyledElement::parseMappedAttribute(attr);
+ }
+}
+
+}
+
+#endif // ENABLE(SVG)
+
+// vim:ts=4:noet