summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/rendering/svg
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/rendering/svg')
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGInline.cpp124
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGInline.h68
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGInlineText.cpp212
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGInlineText.h84
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGPath.cpp338
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGPath.h105
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGTSpan.cpp38
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGTSpan.h38
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGText.cpp260
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGText.h98
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGTextPath.cpp85
-rw-r--r--Source/WebCore/rendering/svg/RenderSVGTextPath.h66
-rw-r--r--Source/WebCore/rendering/svg/SVGInlineFlowBox.cpp144
-rw-r--r--Source/WebCore/rendering/svg/SVGInlineFlowBox.h61
-rw-r--r--Source/WebCore/rendering/svg/SVGInlineTextBox.cpp608
-rw-r--r--Source/WebCore/rendering/svg/SVGInlineTextBox.h92
-rw-r--r--Source/WebCore/rendering/svg/SVGRootInlineBox.cpp222
-rw-r--r--Source/WebCore/rendering/svg/SVGRootInlineBox.h71
-rw-r--r--Source/WebCore/rendering/svg/SVGTextChunk.cpp93
-rw-r--r--Source/WebCore/rendering/svg/SVGTextChunk.h68
-rw-r--r--Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp232
-rw-r--r--Source/WebCore/rendering/svg/SVGTextChunkBuilder.h64
-rw-r--r--Source/WebCore/rendering/svg/SVGTextFragment.h57
-rw-r--r--Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp100
-rw-r--r--Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h69
-rw-r--r--Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp308
-rw-r--r--Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h83
-rw-r--r--Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp579
-rw-r--r--Source/WebCore/rendering/svg/SVGTextLayoutEngine.h94
-rw-r--r--Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp234
-rw-r--r--Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h54
-rw-r--r--Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.cpp96
-rw-r--r--Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.h52
-rw-r--r--Source/WebCore/rendering/svg/SVGTextMetrics.cpp115
-rw-r--r--Source/WebCore/rendering/svg/SVGTextMetrics.h78
-rw-r--r--Source/WebCore/rendering/svg/SVGTextQuery.cpp562
-rw-r--r--Source/WebCore/rendering/svg/SVGTextQuery.h75
37 files changed, 5727 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/svg/RenderSVGInline.cpp b/Source/WebCore/rendering/svg/RenderSVGInline.cpp
new file mode 100644
index 0000000..4d0c533
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGInline.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * Copyright (C) 2006 Apple Inc. All rights reserved.
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "RenderSVGInline.h"
+
+#include "RenderSVGResource.h"
+#include "RenderSVGText.h"
+#include "SVGInlineFlowBox.h"
+
+namespace WebCore {
+
+RenderSVGInline::RenderSVGInline(Node* n)
+ : RenderInline(n)
+{
+}
+
+InlineFlowBox* RenderSVGInline::createInlineFlowBox()
+{
+ InlineFlowBox* box = new (renderArena()) SVGInlineFlowBox(this);
+ box->setHasVirtualLogicalHeight();
+ return box;
+}
+
+FloatRect RenderSVGInline::objectBoundingBox() const
+{
+ if (const RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this))
+ return object->objectBoundingBox();
+
+ return FloatRect();
+}
+
+FloatRect RenderSVGInline::strokeBoundingBox() const
+{
+ if (const RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this))
+ return object->strokeBoundingBox();
+
+ return FloatRect();
+}
+
+FloatRect RenderSVGInline::repaintRectInLocalCoordinates() const
+{
+ if (const RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this))
+ return object->repaintRectInLocalCoordinates();
+
+ return FloatRect();
+}
+
+IntRect RenderSVGInline::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer)
+{
+ return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer);
+}
+
+void RenderSVGInline::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed)
+{
+ SVGRenderSupport::computeRectForRepaint(this, repaintContainer, repaintRect, fixed);
+}
+
+void RenderSVGInline::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState& transformState) const
+{
+ SVGRenderSupport::mapLocalToContainer(this, repaintContainer, useTransforms, fixed, transformState);
+}
+
+void RenderSVGInline::absoluteQuads(Vector<FloatQuad>& quads)
+{
+ RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this);
+ if (!object)
+ return;
+
+ FloatRect textBoundingBox = object->strokeBoundingBox();
+ for (InlineFlowBox* box = firstLineBox(); box; box = box->nextLineBox())
+ quads.append(localToAbsoluteQuad(FloatRect(textBoundingBox.x() + box->x(), textBoundingBox.y() + box->y(), box->logicalWidth(), box->logicalHeight())));
+}
+
+void RenderSVGInline::destroy()
+{
+ SVGResourcesCache::clientDestroyed(this);
+ RenderInline::destroy();
+}
+
+void RenderSVGInline::styleWillChange(StyleDifference diff, const RenderStyle* newStyle)
+{
+ if (diff == StyleDifferenceLayout)
+ setNeedsBoundariesUpdate();
+ RenderInline::styleWillChange(diff, newStyle);
+}
+
+void RenderSVGInline::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
+{
+ RenderInline::styleDidChange(diff, oldStyle);
+ SVGResourcesCache::clientStyleChanged(this, diff, style());
+}
+
+void RenderSVGInline::updateFromElement()
+{
+ RenderInline::updateFromElement();
+ SVGResourcesCache::clientUpdatedFromElement(this, style());
+}
+
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/RenderSVGInline.h b/Source/WebCore/rendering/svg/RenderSVGInline.h
new file mode 100644
index 0000000..d7b7e66
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGInline.h
@@ -0,0 +1,68 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * (C) 2006 Apple Computer Inc.
+ *
+ * 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.
+ *
+ */
+
+#ifndef RenderSVGInline_h
+#define RenderSVGInline_h
+
+#if ENABLE(SVG)
+#include "RenderInline.h"
+
+#include "SVGRenderSupport.h"
+
+namespace WebCore {
+
+class RenderSVGInline : public RenderInline {
+public:
+ explicit RenderSVGInline(Node*);
+
+ virtual const char* renderName() const { return "RenderSVGInline"; }
+ virtual bool requiresLayer() const { return false; }
+ virtual bool isSVGInline() const { return true; }
+
+ // Chapter 10.4 of the SVG Specification say that we should use the
+ // object bounding box of the parent text element.
+ // We search for the root text element and take its bounding box.
+ // It is also necessary to take the stroke and repaint rect of
+ // this element, since we need it for filters.
+ virtual FloatRect objectBoundingBox() const;
+ virtual FloatRect strokeBoundingBox() const;
+ virtual FloatRect repaintRectInLocalCoordinates() const;
+
+ virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer);
+ virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false);
+ virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&) const;
+ virtual void absoluteQuads(Vector<FloatQuad>&);
+
+private:
+ virtual InlineFlowBox* createInlineFlowBox();
+
+ virtual void destroy();
+ virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle);
+ virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle);
+ virtual void updateFromElement();
+};
+
+}
+
+#endif // ENABLE(SVG)
+#endif // !RenderSVGTSpan_H
diff --git a/Source/WebCore/rendering/svg/RenderSVGInlineText.cpp b/Source/WebCore/rendering/svg/RenderSVGInlineText.cpp
new file mode 100644
index 0000000..b791f3e
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGInlineText.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * Copyright (C) 2006 Apple Computer Inc.
+ * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ * Copyright (C) 2008 Rob Buis <buis@kde.org>
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "RenderSVGInlineText.h"
+
+#include "FloatConversion.h"
+#include "FloatQuad.h"
+#include "RenderBlock.h"
+#include "RenderSVGRoot.h"
+#include "RenderSVGText.h"
+#include "SVGInlineTextBox.h"
+#include "SVGRootInlineBox.h"
+#include "VisiblePosition.h"
+
+namespace WebCore {
+
+static PassRefPtr<StringImpl> applySVGWhitespaceRules(PassRefPtr<StringImpl> string, bool preserveWhiteSpace)
+{
+ if (preserveWhiteSpace) {
+ // Spec: When xml:space="preserve", the SVG user agent will do the following using a
+ // copy of the original character data content. It will convert all newline and tab
+ // characters into space characters. Then, it will draw all space characters, including
+ // leading, trailing and multiple contiguous space characters.
+ RefPtr<StringImpl> newString = string->replace('\t', ' ');
+ newString = newString->replace('\n', ' ');
+ newString = newString->replace('\r', ' ');
+ return newString.release();
+ }
+
+ // Spec: When xml:space="default", the SVG user agent will do the following using a
+ // copy of the original character data content. First, it will remove all newline
+ // characters. Then it will convert all tab characters into space characters.
+ // Then, it will strip off all leading and trailing space characters.
+ // Then, all contiguous space characters will be consolidated.
+ RefPtr<StringImpl> newString = string->replace('\n', StringImpl::empty());
+ newString = newString->replace('\r', StringImpl::empty());
+ newString = newString->replace('\t', ' ');
+ return newString.release();
+}
+
+RenderSVGInlineText::RenderSVGInlineText(Node* n, PassRefPtr<StringImpl> string)
+ : RenderText(n, applySVGWhitespaceRules(string, false))
+{
+}
+
+void RenderSVGInlineText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
+{
+ RenderText::styleDidChange(diff, oldStyle);
+
+ if (diff == StyleDifferenceLayout) {
+ // The text metrics may be influenced by style changes.
+ if (RenderSVGText* textRenderer = RenderSVGText::locateRenderSVGTextAncestor(this))
+ textRenderer->setNeedsPositioningValuesUpdate();
+ }
+
+ const RenderStyle* newStyle = style();
+ if (!newStyle || newStyle->whiteSpace() != PRE)
+ return;
+
+ if (!oldStyle || oldStyle->whiteSpace() != PRE)
+ setText(applySVGWhitespaceRules(originalText(), true), true);
+}
+
+InlineTextBox* RenderSVGInlineText::createTextBox()
+{
+ InlineTextBox* box = new (renderArena()) SVGInlineTextBox(this);
+ box->setHasVirtualLogicalHeight();
+ return box;
+}
+
+IntRect RenderSVGInlineText::localCaretRect(InlineBox* box, int caretOffset, int*)
+{
+ if (!box->isInlineTextBox())
+ return IntRect();
+
+ InlineTextBox* textBox = static_cast<InlineTextBox*>(box);
+ if (static_cast<unsigned>(caretOffset) < textBox->start() || static_cast<unsigned>(caretOffset) > textBox->start() + textBox->len())
+ return IntRect();
+
+ // Use the edge of the selection rect to determine the caret rect.
+ if (static_cast<unsigned>(caretOffset) < textBox->start() + textBox->len()) {
+ IntRect rect = textBox->selectionRect(0, 0, caretOffset, caretOffset + 1);
+ int x = box->isLeftToRightDirection() ? rect.x() : rect.right();
+ return IntRect(x, rect.y(), caretWidth, rect.height());
+ }
+
+ IntRect rect = textBox->selectionRect(0, 0, caretOffset - 1, caretOffset);
+ int x = box->isLeftToRightDirection() ? rect.right() : rect.x();
+ return IntRect(x, rect.y(), caretWidth, rect.height());
+}
+
+IntRect RenderSVGInlineText::linesBoundingBox() const
+{
+ IntRect boundingBox;
+ for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
+ boundingBox.unite(box->calculateBoundaries());
+ return boundingBox;
+}
+
+bool RenderSVGInlineText::characterStartsNewTextChunk(int position) const
+{
+ ASSERT(m_attributes.xValues().size() == textLength());
+ ASSERT(m_attributes.yValues().size() == textLength());
+ ASSERT(position >= 0);
+ ASSERT(position < static_cast<int>(textLength()));
+
+ // Each <textPath> element starts a new text chunk, regardless of any x/y values.
+ if (!position && parent()->isSVGTextPath() && !previousSibling())
+ return true;
+
+ int currentPosition = 0;
+ unsigned size = m_attributes.textMetricsValues().size();
+ for (unsigned i = 0; i < size; ++i) {
+ const SVGTextMetrics& metrics = m_attributes.textMetricsValues().at(i);
+
+ // We found the desired character.
+ if (currentPosition == position) {
+ return m_attributes.xValues().at(position) != SVGTextLayoutAttributes::emptyValue()
+ || m_attributes.yValues().at(position) != SVGTextLayoutAttributes::emptyValue();
+ }
+
+ currentPosition += metrics.length();
+ if (currentPosition > position)
+ break;
+ }
+
+ // The desired position is available in the x/y list, but not in the character data values list.
+ // That means the previous character data described a single glyph, consisting of multiple unicode characters.
+ // The consequence is that the desired character does not define a new absolute x/y position, even if present in the x/y test.
+ // This code is tested by svg/W3C-SVG-1.1/text-text-06-t.svg (and described in detail, why this influences chunk detection).
+ return false;
+}
+
+VisiblePosition RenderSVGInlineText::positionForPoint(const IntPoint& point)
+{
+ if (!firstTextBox() || !textLength())
+ return createVisiblePosition(0, DOWNSTREAM);
+
+ RenderStyle* style = this->style();
+ ASSERT(style);
+ int baseline = style->font().ascent();
+
+ RenderBlock* containingBlock = this->containingBlock();
+ ASSERT(containingBlock);
+
+ // Map local point to absolute point, as the character origins stored in the text fragments use absolute coordinates.
+ FloatPoint absolutePoint(point);
+ absolutePoint.move(containingBlock->x(), containingBlock->y());
+
+ float closestDistance = std::numeric_limits<float>::max();
+ float closestDistancePosition = 0;
+ const SVGTextFragment* closestDistanceFragment = 0;
+ SVGInlineTextBox* closestDistanceBox = 0;
+
+ for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
+ ASSERT(box->isSVGInlineTextBox());
+ SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(box);
+ Vector<SVGTextFragment>& fragments = textBox->textFragments();
+
+ unsigned textFragmentsSize = fragments.size();
+ for (unsigned i = 0; i < textFragmentsSize; ++i) {
+ const SVGTextFragment& fragment = fragments.at(i);
+ FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height);
+ if (!fragment.transform.isIdentity())
+ fragmentRect = fragment.transform.mapRect(fragmentRect);
+
+ float distance = powf(fragmentRect.x() - absolutePoint.x(), 2) +
+ powf(fragmentRect.y() + fragmentRect.height() / 2 - absolutePoint.y(), 2);
+
+ if (distance < closestDistance) {
+ closestDistance = distance;
+ closestDistanceBox = textBox;
+ closestDistanceFragment = &fragment;
+ closestDistancePosition = fragmentRect.x();
+ }
+ }
+ }
+
+ if (!closestDistanceFragment)
+ return createVisiblePosition(0, DOWNSTREAM);
+
+ int offset = closestDistanceBox->offsetForPositionInFragment(*closestDistanceFragment, absolutePoint.x() - closestDistancePosition, true);
+ return createVisiblePosition(offset + closestDistanceBox->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM);
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/RenderSVGInlineText.h b/Source/WebCore/rendering/svg/RenderSVGInlineText.h
new file mode 100644
index 0000000..926ec43
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGInlineText.h
@@ -0,0 +1,84 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
+ * (C) 2008 Rob Buis <buis@kde.org>
+ *
+ * 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.
+ *
+ */
+
+#ifndef RenderSVGInlineText_h
+#define RenderSVGInlineText_h
+
+#if ENABLE(SVG)
+#include "RenderText.h"
+#include "SVGTextLayoutAttributes.h"
+
+namespace WebCore {
+
+class SVGInlineTextBox;
+
+class RenderSVGInlineText : public RenderText {
+public:
+ RenderSVGInlineText(Node*, PassRefPtr<StringImpl>);
+
+ bool characterStartsNewTextChunk(int position) const;
+
+ SVGTextLayoutAttributes& layoutAttributes() { return m_attributes; }
+ const SVGTextLayoutAttributes& layoutAttributes() const { return m_attributes; }
+ void storeLayoutAttributes(const SVGTextLayoutAttributes& attributes) { m_attributes = attributes; }
+
+private:
+ virtual const char* renderName() const { return "RenderSVGInlineText"; }
+
+ virtual void styleDidChange(StyleDifference, const RenderStyle*);
+
+ // FIXME: We need objectBoundingBox for DRT results and filters at the moment.
+ // This should be fixed to give back the objectBoundingBox of the text root.
+ virtual FloatRect objectBoundingBox() const { return FloatRect(); }
+
+ virtual bool requiresLayer() const { return false; }
+ virtual bool isSVGInlineText() const { return true; }
+
+ virtual VisiblePosition positionForPoint(const IntPoint&);
+ virtual IntRect localCaretRect(InlineBox*, int caretOffset, int* extraWidthToEndOfLine = 0);
+ virtual IntRect linesBoundingBox() const;
+ virtual InlineTextBox* createTextBox();
+
+ SVGTextLayoutAttributes m_attributes;
+};
+
+inline RenderSVGInlineText* toRenderSVGInlineText(RenderObject* object)
+{
+ ASSERT(!object || object->isSVGInlineText());
+ return static_cast<RenderSVGInlineText*>(object);
+}
+
+inline const RenderSVGInlineText* toRenderSVGInlineText(const RenderObject* object)
+{
+ ASSERT(!object || object->isSVGInlineText());
+ return static_cast<const RenderSVGInlineText*>(object);
+}
+
+// This will catch anyone doing an unnecessary cast.
+void toRenderSVGInlineText(const RenderSVGInlineText*);
+
+}
+
+#endif // ENABLE(SVG)
+#endif // RenderSVGInlineText_h
diff --git a/Source/WebCore/rendering/svg/RenderSVGPath.cpp b/Source/WebCore/rendering/svg/RenderSVGPath.cpp
new file mode 100644
index 0000000..0c8ac0c
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGPath.cpp
@@ -0,0 +1,338 @@
+/*
+ Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ 2004, 2005, 2008 Rob Buis <buis@kde.org>
+ 2005, 2007 Eric Seidel <eric@webkit.org>
+ 2009 Google, Inc.
+ 2009 Dirk Schulze <krit@webkit.org>
+ Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ 2009 Jeff Schiller <codedread@gmail.com>
+
+ 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
+ aint 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 "RenderSVGPath.h"
+
+#include "FloatPoint.h"
+#include "FloatQuad.h"
+#include "GraphicsContext.h"
+#include "HitTestRequest.h"
+#include "PointerEventsHitRules.h"
+#include "RenderSVGContainer.h"
+#include "RenderSVGResourceMarker.h"
+#include "RenderSVGResourceSolidColor.h"
+#include "SVGRenderSupport.h"
+#include "SVGResources.h"
+#include "SVGStyledTransformableElement.h"
+#include "SVGTransformList.h"
+#include "SVGURIReference.h"
+#include "StrokeStyleApplier.h"
+#include <wtf/MathExtras.h>
+
+namespace WebCore {
+
+class BoundingRectStrokeStyleApplier : public StrokeStyleApplier {
+public:
+ BoundingRectStrokeStyleApplier(const RenderObject* object, RenderStyle* style)
+ : m_object(object)
+ , m_style(style)
+ {
+ ASSERT(style);
+ ASSERT(object);
+ }
+
+ void strokeStyle(GraphicsContext* gc)
+ {
+ SVGRenderSupport::applyStrokeStyleToContext(gc, m_style, m_object);
+ }
+
+private:
+ const RenderObject* m_object;
+ RenderStyle* m_style;
+};
+
+RenderSVGPath::RenderSVGPath(SVGStyledTransformableElement* node)
+ : RenderSVGModelObject(node)
+ , m_needsBoundariesUpdate(false) // default is false, the cached rects are empty from the beginning
+ , m_needsPathUpdate(true) // default is true, so we grab a Path object once from SVGStyledTransformableElement
+ , m_needsTransformUpdate(true) // default is true, so we grab a AffineTransform object once from SVGStyledTransformableElement
+{
+}
+
+RenderSVGPath::~RenderSVGPath()
+{
+}
+
+bool RenderSVGPath::fillContains(const FloatPoint& point, bool requiresFill, WindRule fillRule)
+{
+ if (!m_fillBoundingBox.contains(point))
+ return false;
+
+ Color fallbackColor;
+ if (requiresFill && !RenderSVGResource::fillPaintingResource(this, style(), fallbackColor))
+ return false;
+
+ return m_path.contains(point, fillRule);
+}
+
+bool RenderSVGPath::strokeContains(const FloatPoint& point, bool requiresStroke)
+{
+ if (!m_strokeAndMarkerBoundingBox.contains(point))
+ return false;
+
+ Color fallbackColor;
+ if (requiresStroke && !RenderSVGResource::strokePaintingResource(this, style(), fallbackColor))
+ return false;
+
+ BoundingRectStrokeStyleApplier strokeStyle(this, style());
+ return m_path.strokeContains(&strokeStyle, point);
+}
+
+void RenderSVGPath::layout()
+{
+ LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && selfNeedsLayout());
+ SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node());
+
+ bool updateCachedBoundariesInParents = false;
+
+ bool needsPathUpdate = m_needsPathUpdate;
+ if (needsPathUpdate) {
+ m_path.clear();
+ element->toPathData(m_path);
+ m_needsPathUpdate = false;
+ updateCachedBoundariesInParents = true;
+ }
+
+ if (m_needsTransformUpdate) {
+ m_localTransform = element->animatedLocalTransform();
+ m_needsTransformUpdate = false;
+ updateCachedBoundariesInParents = true;
+ }
+
+ if (m_needsBoundariesUpdate)
+ updateCachedBoundariesInParents = true;
+
+ // Invalidate all resources of this client if our layout changed.
+ if (m_everHadLayout && selfNeedsLayout())
+ SVGResourcesCache::clientLayoutChanged(this);
+
+ // At this point LayoutRepainter already grabbed the old bounds,
+ // recalculate them now so repaintAfterLayout() uses the new bounds.
+ if (needsPathUpdate || m_needsBoundariesUpdate) {
+ updateCachedBoundaries();
+ m_needsBoundariesUpdate = false;
+ }
+
+ // If our bounds changed, notify the parents.
+ if (updateCachedBoundariesInParents)
+ RenderSVGModelObject::setNeedsBoundariesUpdate();
+
+ repainter.repaintAfterLayout();
+ setNeedsLayout(false);
+}
+
+void RenderSVGPath::fillAndStrokePath(GraphicsContext* context)
+{
+ RenderStyle* style = this->style();
+
+ Color fallbackColor;
+ if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(this, style, fallbackColor)) {
+ if (fillPaintingResource->applyResource(this, style, context, ApplyToFillMode))
+ fillPaintingResource->postApplyResource(this, context, ApplyToFillMode, &m_path);
+ else if (fallbackColor.isValid()) {
+ RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource();
+ fallbackResource->setColor(fallbackColor);
+ if (fallbackResource->applyResource(this, style, context, ApplyToFillMode))
+ fallbackResource->postApplyResource(this, context, ApplyToFillMode, &m_path);
+ }
+ }
+
+ fallbackColor = Color();
+ RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(this, style, fallbackColor);
+ if (!strokePaintingResource)
+ return;
+
+ Path path;
+
+ bool nonScalingStroke = style->svgStyle()->vectorEffect() == VE_NON_SCALING_STROKE;
+ bool restoreContext = false;
+ if (nonScalingStroke) {
+ SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node());
+ AffineTransform nonScalingStrokeTransform = element->getScreenCTM(SVGLocatable::DisallowStyleUpdate);
+ if (!nonScalingStrokeTransform.isInvertible())
+ return;
+
+ path = m_path;
+ path.transform(nonScalingStrokeTransform);
+
+ context->save();
+ context->concatCTM(nonScalingStrokeTransform.inverse());
+ restoreContext = true;
+ }
+
+ if (strokePaintingResource->applyResource(this, style, context, ApplyToStrokeMode))
+ strokePaintingResource->postApplyResource(this, context, ApplyToStrokeMode, nonScalingStroke ? &path : &m_path);
+ else if (fallbackColor.isValid()) {
+ RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource();
+ fallbackResource->setColor(fallbackColor);
+ if (fallbackResource->applyResource(this, style, context, ApplyToStrokeMode))
+ fallbackResource->postApplyResource(this, context, ApplyToStrokeMode, nonScalingStroke ? &path : &m_path);
+ }
+
+ if (restoreContext)
+ context->restore();
+}
+
+void RenderSVGPath::paint(PaintInfo& paintInfo, int, int)
+{
+ if (paintInfo.context->paintingDisabled() || style()->visibility() == HIDDEN || m_path.isEmpty())
+ return;
+
+ FloatRect boundingBox = repaintRectInLocalCoordinates();
+ if (!SVGRenderSupport::paintInfoIntersectsRepaintRect(boundingBox, m_localTransform, paintInfo))
+ return;
+
+ PaintInfo childPaintInfo(paintInfo);
+ bool drawsOutline = style()->outlineWidth() && (childPaintInfo.phase == PaintPhaseOutline || childPaintInfo.phase == PaintPhaseSelfOutline);
+ if (drawsOutline || childPaintInfo.phase == PaintPhaseForeground) {
+ childPaintInfo.context->save();
+ childPaintInfo.applyTransform(m_localTransform);
+
+ if (childPaintInfo.phase == PaintPhaseForeground) {
+ PaintInfo savedInfo(childPaintInfo);
+
+ if (SVGRenderSupport::prepareToRenderSVGContent(this, childPaintInfo)) {
+ const SVGRenderStyle* svgStyle = style()->svgStyle();
+ if (svgStyle->shapeRendering() == SR_CRISPEDGES)
+ childPaintInfo.context->setShouldAntialias(false);
+
+ fillAndStrokePath(childPaintInfo.context);
+
+ if (svgStyle->hasMarkers())
+ m_markerLayoutInfo.drawMarkers(childPaintInfo);
+ }
+
+ SVGRenderSupport::finishRenderSVGContent(this, childPaintInfo, savedInfo.context);
+ }
+
+ if (drawsOutline)
+ paintOutline(childPaintInfo.context, static_cast<int>(boundingBox.x()), static_cast<int>(boundingBox.y()),
+ static_cast<int>(boundingBox.width()), static_cast<int>(boundingBox.height()));
+
+ childPaintInfo.context->restore();
+ }
+}
+
+// This method is called from inside paintOutline() since we call paintOutline()
+// while transformed to our coord system, return local coords
+void RenderSVGPath::addFocusRingRects(Vector<IntRect>& rects, int, int)
+{
+ IntRect rect = enclosingIntRect(repaintRectInLocalCoordinates());
+ if (!rect.isEmpty())
+ rects.append(rect);
+}
+
+bool RenderSVGPath::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction)
+{
+ // We only draw in the forground phase, so we only hit-test then.
+ if (hitTestAction != HitTestForeground)
+ return false;
+
+ FloatPoint localPoint = m_localTransform.inverse().mapPoint(pointInParent);
+
+ if (!SVGRenderSupport::pointInClippingArea(this, localPoint))
+ return false;
+
+ PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_PATH_HITTESTING, request, style()->pointerEvents());
+ bool isVisible = (style()->visibility() == VISIBLE);
+ if (isVisible || !hitRules.requireVisible) {
+ const SVGRenderStyle* svgStyle = style()->svgStyle();
+ WindRule fillRule = svgStyle->fillRule();
+ if (request.svgClipContent())
+ fillRule = svgStyle->clipRule();
+ if ((hitRules.canHitStroke && (svgStyle->hasStroke() || !hitRules.requireStroke) && strokeContains(localPoint, hitRules.requireStroke))
+ || (hitRules.canHitFill && (svgStyle->hasFill() || !hitRules.requireFill) && fillContains(localPoint, hitRules.requireFill, fillRule))) {
+ updateHitTestResult(result, roundedIntPoint(localPoint));
+ return true;
+ }
+ }
+ return false;
+}
+
+FloatRect RenderSVGPath::calculateMarkerBoundsIfNeeded()
+{
+ SVGElement* svgElement = static_cast<SVGElement*>(node());
+ ASSERT(svgElement && svgElement->document());
+ if (!svgElement->isStyled())
+ return FloatRect();
+
+ SVGStyledElement* styledElement = static_cast<SVGStyledElement*>(svgElement);
+ if (!styledElement->supportsMarkers())
+ return FloatRect();
+
+ const SVGRenderStyle* svgStyle = style()->svgStyle();
+ ASSERT(svgStyle->hasMarkers());
+
+ SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this);
+ if (!resources)
+ return FloatRect();
+
+ RenderSVGResourceMarker* markerStart = resources->markerStart();
+ RenderSVGResourceMarker* markerMid = resources->markerMid();
+ RenderSVGResourceMarker* markerEnd = resources->markerEnd();
+ if (!markerStart && !markerMid && !markerEnd)
+ return FloatRect();
+
+ return m_markerLayoutInfo.calculateBoundaries(markerStart, markerMid, markerEnd, svgStyle->strokeWidth().value(svgElement), m_path);
+}
+
+void RenderSVGPath::updateCachedBoundaries()
+{
+ if (m_path.isEmpty()) {
+ m_fillBoundingBox = FloatRect();
+ m_strokeAndMarkerBoundingBox = FloatRect();
+ m_repaintBoundingBox = FloatRect();
+ return;
+ }
+
+ // Cache _unclipped_ fill bounding box, used for calculations in resources
+ m_fillBoundingBox = m_path.boundingRect();
+
+ // Cache _unclipped_ stroke bounding box, used for calculations in resources (includes marker boundaries)
+ m_strokeAndMarkerBoundingBox = m_fillBoundingBox;
+
+ const SVGRenderStyle* svgStyle = style()->svgStyle();
+ if (svgStyle->hasStroke()) {
+ BoundingRectStrokeStyleApplier strokeStyle(this, style());
+ m_strokeAndMarkerBoundingBox.unite(m_path.strokeBoundingRect(&strokeStyle));
+ }
+
+ if (svgStyle->hasMarkers()) {
+ FloatRect markerBounds = calculateMarkerBoundsIfNeeded();
+ if (!markerBounds.isEmpty())
+ m_strokeAndMarkerBoundingBox.unite(markerBounds);
+ }
+
+ // Cache smallest possible repaint rectangle
+ m_repaintBoundingBox = m_strokeAndMarkerBoundingBox;
+ SVGRenderSupport::intersectRepaintRectWithResources(this, m_repaintBoundingBox);
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/RenderSVGPath.h b/Source/WebCore/rendering/svg/RenderSVGPath.h
new file mode 100644
index 0000000..41b0e51
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGPath.h
@@ -0,0 +1,105 @@
+/*
+ Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ 2004, 2005 Rob Buis <buis@kde.org>
+ 2005 Eric Seidel <eric@webkit.org>
+ 2006 Apple Computer, Inc
+ 2009 Google, Inc.
+
+ 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
+ aint 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.
+*/
+
+#ifndef RenderSVGPath_h
+#define RenderSVGPath_h
+
+#if ENABLE(SVG)
+#include "AffineTransform.h"
+#include "FloatRect.h"
+#include "RenderSVGModelObject.h"
+#include "SVGMarkerLayoutInfo.h"
+
+namespace WebCore {
+
+class FloatPoint;
+class RenderSVGContainer;
+class SVGStyledTransformableElement;
+
+class RenderSVGPath : public RenderSVGModelObject {
+public:
+ explicit RenderSVGPath(SVGStyledTransformableElement*);
+ virtual ~RenderSVGPath();
+
+ const Path& path() const { return m_path; }
+ void setNeedsPathUpdate() { m_needsPathUpdate = true; }
+ virtual void setNeedsBoundariesUpdate() { m_needsBoundariesUpdate = true; }
+ virtual void setNeedsTransformUpdate() { m_needsTransformUpdate = true; }
+
+private:
+ // Hit-detection seperated for the fill and the stroke
+ bool fillContains(const FloatPoint&, bool requiresFill = true, WindRule fillRule = RULE_NONZERO);
+ bool strokeContains(const FloatPoint&, bool requiresStroke = true);
+
+ virtual FloatRect objectBoundingBox() const { return m_fillBoundingBox; }
+ virtual FloatRect strokeBoundingBox() const { return m_strokeAndMarkerBoundingBox; }
+ virtual FloatRect repaintRectInLocalCoordinates() const { return m_repaintBoundingBox; }
+ virtual const AffineTransform& localToParentTransform() const { return m_localTransform; }
+
+ virtual bool isSVGPath() const { return true; }
+ virtual const char* renderName() const { return "RenderSVGPath"; }
+
+ virtual void layout();
+ virtual void paint(PaintInfo&, int parentX, int parentY);
+ virtual void addFocusRingRects(Vector<IntRect>&, int tx, int ty);
+
+ virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction);
+
+ FloatRect calculateMarkerBoundsIfNeeded();
+ void updateCachedBoundaries();
+
+private:
+ virtual AffineTransform localTransform() const { return m_localTransform; }
+ void fillAndStrokePath(GraphicsContext*);
+
+ bool m_needsBoundariesUpdate : 1;
+ bool m_needsPathUpdate : 1;
+ bool m_needsTransformUpdate : 1;
+
+ mutable Path m_path;
+ FloatRect m_fillBoundingBox;
+ FloatRect m_strokeAndMarkerBoundingBox;
+ FloatRect m_repaintBoundingBox;
+ SVGMarkerLayoutInfo m_markerLayoutInfo;
+ AffineTransform m_localTransform;
+};
+
+inline RenderSVGPath* toRenderSVGPath(RenderObject* object)
+{
+ ASSERT(!object || object->isSVGPath());
+ return static_cast<RenderSVGPath*>(object);
+}
+
+inline const RenderSVGPath* toRenderSVGPath(const RenderObject* object)
+{
+ ASSERT(!object || object->isSVGPath());
+ return static_cast<const RenderSVGPath*>(object);
+}
+
+// This will catch anyone doing an unnecessary cast.
+void toRenderSVGPath(const RenderSVGPath*);
+
+}
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/RenderSVGTSpan.cpp b/Source/WebCore/rendering/svg/RenderSVGTSpan.cpp
new file mode 100644
index 0000000..90ff36c
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGTSpan.cpp
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * (C) 2006 Apple Computer Inc.
+ *
+ * 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 "RenderSVGTSpan.h"
+
+namespace WebCore {
+
+RenderSVGTSpan::RenderSVGTSpan(Node* n)
+ : RenderSVGInline(n)
+{
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/RenderSVGTSpan.h b/Source/WebCore/rendering/svg/RenderSVGTSpan.h
new file mode 100644
index 0000000..97e0eb0
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGTSpan.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * (C) 2006 Apple Computer Inc.
+ * (C) 2009 Google Inc.
+ *
+ * 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.
+ *
+ */
+
+#ifndef RenderSVGTSpan_h
+#define RenderSVGTSpan_h
+
+#if ENABLE(SVG)
+#include "RenderSVGInline.h"
+
+namespace WebCore {
+class RenderSVGTSpan : public RenderSVGInline {
+public:
+ explicit RenderSVGTSpan(Node*);
+ virtual const char* renderName() const { return "RenderSVGTSpan"; }
+};
+}
+
+#endif // ENABLE(SVG)
+#endif // !RenderSVGTSpan_h
diff --git a/Source/WebCore/rendering/svg/RenderSVGText.cpp b/Source/WebCore/rendering/svg/RenderSVGText.cpp
new file mode 100644
index 0000000..01a92b0
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGText.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2006 Apple Computer, Inc.
+ * Copyright (C) 2006 Alexander Kellett <lypanov@kde.org>
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ * Copyright (C) 2008 Rob Buis <buis@kde.org>
+ * Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "RenderSVGText.h"
+
+#include "FloatConversion.h"
+#include "FloatQuad.h"
+#include "GraphicsContext.h"
+#include "HitTestRequest.h"
+#include "PointerEventsHitRules.h"
+#include "RenderLayer.h"
+#include "RenderSVGResource.h"
+#include "RenderSVGRoot.h"
+#include "SVGLengthList.h"
+#include "SVGRenderSupport.h"
+#include "SVGRootInlineBox.h"
+#include "SVGTextElement.h"
+#include "SVGTextLayoutAttributesBuilder.h"
+#include "SVGTransformList.h"
+#include "SVGURIReference.h"
+#include "SimpleFontData.h"
+#include "TransformState.h"
+#include "VisiblePosition.h"
+
+namespace WebCore {
+
+RenderSVGText::RenderSVGText(SVGTextElement* node)
+ : RenderSVGBlock(node)
+ , m_needsPositioningValuesUpdate(true)
+ , m_needsTransformUpdate(true)
+{
+}
+
+RenderSVGText* RenderSVGText::locateRenderSVGTextAncestor(RenderObject* start)
+{
+ ASSERT(start);
+ while (start && !start->isSVGText())
+ start = start->parent();
+ if (!start || !start->isSVGText())
+ return 0;
+ return toRenderSVGText(start);
+}
+
+const RenderSVGText* RenderSVGText::locateRenderSVGTextAncestor(const RenderObject* start)
+{
+ ASSERT(start);
+ while (start && !start->isSVGText())
+ start = start->parent();
+ if (!start || !start->isSVGText())
+ return 0;
+ return toRenderSVGText(start);
+}
+
+IntRect RenderSVGText::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer)
+{
+ return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer);
+}
+
+void RenderSVGText::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed)
+{
+ SVGRenderSupport::computeRectForRepaint(this, repaintContainer, repaintRect, fixed);
+}
+
+void RenderSVGText::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool useTransforms, TransformState& transformState) const
+{
+ SVGRenderSupport::mapLocalToContainer(this, repaintContainer, fixed, useTransforms, transformState);
+}
+
+void RenderSVGText::layout()
+{
+ ASSERT(needsLayout());
+ LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
+
+ bool updateCachedBoundariesInParents = false;
+ if (m_needsTransformUpdate) {
+ SVGTextElement* text = static_cast<SVGTextElement*>(node());
+ m_localTransform = text->animatedLocalTransform();
+ m_needsTransformUpdate = false;
+ updateCachedBoundariesInParents = true;
+ }
+
+ if (m_needsPositioningValuesUpdate) {
+ // Perform SVG text layout phase one (see SVGTextLayoutAttributesBuilder for details).
+ SVGTextLayoutAttributesBuilder layoutAttributesBuilder;
+ layoutAttributesBuilder.buildLayoutAttributesForTextSubtree(this);
+ m_needsPositioningValuesUpdate = false;
+ updateCachedBoundariesInParents = true;
+ }
+
+ // Reduced version of RenderBlock::layoutBlock(), which only takes care of SVG text.
+ // All if branches that could cause early exit in RenderBlocks layoutBlock() method are turned into assertions.
+ ASSERT(!isInline());
+ ASSERT(!layoutOnlyPositionedObjects());
+ ASSERT(!scrollsOverflow());
+ ASSERT(!hasControlClip());
+ ASSERT(!hasColumns());
+ ASSERT(!positionedObjects());
+ ASSERT(!m_overflow);
+ ASSERT(!isAnonymousBlock());
+
+ if (!firstChild())
+ setChildrenInline(true);
+
+ // FIXME: We need to find a way to only layout the child boxes, if needed.
+ FloatRect oldBoundaries = objectBoundingBox();
+ ASSERT(childrenInline());
+ forceLayoutInlineChildren();
+
+ if (!updateCachedBoundariesInParents)
+ updateCachedBoundariesInParents = oldBoundaries != objectBoundingBox();
+
+ // Invalidate all resources of this client if our layout changed.
+ if (m_everHadLayout && selfNeedsLayout())
+ SVGResourcesCache::clientLayoutChanged(this);
+
+ // If our bounds changed, notify the parents.
+ if (updateCachedBoundariesInParents)
+ RenderSVGBlock::setNeedsBoundariesUpdate();
+
+ repainter.repaintAfterLayout();
+ setNeedsLayout(false);
+}
+
+RootInlineBox* RenderSVGText::createRootInlineBox()
+{
+ RootInlineBox* box = new (renderArena()) SVGRootInlineBox(this);
+ box->setHasVirtualLogicalHeight();
+ return box;
+}
+
+bool RenderSVGText::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction)
+{
+ PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, request, style()->pointerEvents());
+ bool isVisible = (style()->visibility() == VISIBLE);
+ if (isVisible || !hitRules.requireVisible) {
+ if ((hitRules.canHitStroke && (style()->svgStyle()->hasStroke() || !hitRules.requireStroke))
+ || (hitRules.canHitFill && (style()->svgStyle()->hasFill() || !hitRules.requireFill))) {
+ FloatPoint localPoint = localToParentTransform().inverse().mapPoint(pointInParent);
+
+ if (!SVGRenderSupport::pointInClippingArea(this, localPoint))
+ return false;
+
+ return RenderBlock::nodeAtPoint(request, result, (int)localPoint.x(), (int)localPoint.y(), 0, 0, hitTestAction);
+ }
+ }
+
+ return false;
+}
+
+bool RenderSVGText::nodeAtPoint(const HitTestRequest&, HitTestResult&, int, int, int, int, HitTestAction)
+{
+ ASSERT_NOT_REACHED();
+ return false;
+}
+
+VisiblePosition RenderSVGText::positionForPoint(const IntPoint& pointInContents)
+{
+ RootInlineBox* rootBox = firstRootBox();
+ if (!rootBox)
+ return createVisiblePosition(0, DOWNSTREAM);
+
+ ASSERT(rootBox->isSVGRootInlineBox());
+ ASSERT(!rootBox->nextRootBox());
+ ASSERT(childrenInline());
+
+ InlineBox* closestBox = static_cast<SVGRootInlineBox*>(rootBox)->closestLeafChildForPosition(pointInContents);
+ if (!closestBox)
+ return createVisiblePosition(0, DOWNSTREAM);
+
+ return closestBox->renderer()->positionForPoint(IntPoint(pointInContents.x(), closestBox->m_y));
+}
+
+void RenderSVGText::absoluteQuads(Vector<FloatQuad>& quads)
+{
+ quads.append(localToAbsoluteQuad(strokeBoundingBox()));
+}
+
+void RenderSVGText::paint(PaintInfo& paintInfo, int, int)
+{
+ if (paintInfo.context->paintingDisabled())
+ return;
+
+ if (paintInfo.phase != PaintPhaseForeground
+ && paintInfo.phase != PaintPhaseSelfOutline
+ && paintInfo.phase != PaintPhaseSelection)
+ return;
+
+ PaintInfo blockInfo(paintInfo);
+ blockInfo.context->save();
+ blockInfo.applyTransform(localToParentTransform());
+ RenderBlock::paint(blockInfo, 0, 0);
+ blockInfo.context->restore();
+}
+
+FloatRect RenderSVGText::strokeBoundingBox() const
+{
+ FloatRect strokeBoundaries = objectBoundingBox();
+ const SVGRenderStyle* svgStyle = style()->svgStyle();
+ if (!svgStyle->hasStroke())
+ return strokeBoundaries;
+
+ ASSERT(node());
+ ASSERT(node()->isSVGElement());
+ strokeBoundaries.inflate(svgStyle->strokeWidth().value(static_cast<SVGElement*>(node())));
+ return strokeBoundaries;
+}
+
+FloatRect RenderSVGText::repaintRectInLocalCoordinates() const
+{
+ FloatRect repaintRect = strokeBoundingBox();
+ SVGRenderSupport::intersectRepaintRectWithResources(this, repaintRect);
+
+ if (const ShadowData* textShadow = style()->textShadow())
+ textShadow->adjustRectForShadow(repaintRect);
+
+ return repaintRect;
+}
+
+// Fix for <rdar://problem/8048875>. We should not render :first-line CSS Style
+// in a SVG text element context.
+RenderBlock* RenderSVGText::firstLineBlock() const
+{
+ return 0;
+}
+
+// Fix for <rdar://problem/8048875>. We should not render :first-letter CSS Style
+// in a SVG text element context.
+void RenderSVGText::updateFirstLetter()
+{
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/RenderSVGText.h b/Source/WebCore/rendering/svg/RenderSVGText.h
new file mode 100644
index 0000000..deae78c
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGText.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2006 Apple Computer, Inc.
+ * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef RenderSVGText_h
+#define RenderSVGText_h
+
+#if ENABLE(SVG)
+
+#include "AffineTransform.h"
+#include "RenderSVGBlock.h"
+
+namespace WebCore {
+
+class SVGTextElement;
+
+class RenderSVGText : public RenderSVGBlock {
+public:
+ RenderSVGText(SVGTextElement* node);
+
+ void setNeedsPositioningValuesUpdate() { m_needsPositioningValuesUpdate = true; }
+ virtual void setNeedsTransformUpdate() { m_needsTransformUpdate = true; }
+ virtual FloatRect repaintRectInLocalCoordinates() const;
+
+ static RenderSVGText* locateRenderSVGTextAncestor(RenderObject*);
+ static const RenderSVGText* locateRenderSVGTextAncestor(const RenderObject*);
+
+private:
+ virtual const char* renderName() const { return "RenderSVGText"; }
+ virtual bool isSVGText() const { return true; }
+
+ virtual void paint(PaintInfo&, int tx, int ty);
+ virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction);
+ virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction);
+ virtual VisiblePosition positionForPoint(const IntPoint&);
+
+ virtual bool requiresLayer() const { return false; }
+ virtual void layout();
+
+ virtual void absoluteQuads(Vector<FloatQuad>&);
+
+ virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer);
+ virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false);
+
+ virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&) const;
+
+ virtual FloatRect objectBoundingBox() const { return frameRect(); }
+ virtual FloatRect strokeBoundingBox() const;
+
+ virtual const AffineTransform& localToParentTransform() const { return m_localTransform; }
+ virtual AffineTransform localTransform() const { return m_localTransform; }
+ virtual RootInlineBox* createRootInlineBox();
+
+ virtual RenderBlock* firstLineBlock() const;
+ virtual void updateFirstLetter();
+
+ bool m_needsPositioningValuesUpdate : 1;
+ bool m_needsTransformUpdate : 1;
+ AffineTransform m_localTransform;
+};
+
+inline RenderSVGText* toRenderSVGText(RenderObject* object)
+{
+ ASSERT(!object || object->isSVGText());
+ return static_cast<RenderSVGText*>(object);
+}
+
+inline const RenderSVGText* toRenderSVGText(const RenderObject* object)
+{
+ ASSERT(!object || object->isSVGText());
+ return static_cast<const RenderSVGText*>(object);
+}
+
+// This will catch anyone doing an unnecessary cast.
+void toRenderSVGText(const RenderSVGText*);
+
+}
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/RenderSVGTextPath.cpp b/Source/WebCore/rendering/svg/RenderSVGTextPath.cpp
new file mode 100644
index 0000000..4ba2eeb
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGTextPath.cpp
@@ -0,0 +1,85 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ *
+ * 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 "RenderSVGTextPath.h"
+
+#include "FloatQuad.h"
+#include "RenderBlock.h"
+#include "SVGInlineTextBox.h"
+#include "SVGNames.h"
+#include "SVGPathElement.h"
+#include "SVGRootInlineBox.h"
+#include "SVGTextPathElement.h"
+#include "SVGTransformList.h"
+
+namespace WebCore {
+
+RenderSVGTextPath::RenderSVGTextPath(Node* n)
+ : RenderSVGInline(n)
+ , m_startOffset(0.0f)
+ , m_exactAlignment(true)
+ , m_stretchMethod(false)
+{
+}
+
+Path RenderSVGTextPath::layoutPath() const
+{
+ SVGTextPathElement* textPathElement = static_cast<SVGTextPathElement*>(node());
+ String pathId = SVGURIReference::getTarget(textPathElement->href());
+ Element* targetElement = textPathElement->document()->getElementById(pathId);
+ if (!targetElement || !targetElement->hasTagName(SVGNames::pathTag))
+ return Path();
+
+ SVGPathElement* pathElement = static_cast<SVGPathElement*>(targetElement);
+
+ Path pathData;
+ pathElement->toPathData(pathData);
+ // Spec: The transform attribute on the referenced 'path' element represents a
+ // supplemental transformation relative to the current user coordinate system for
+ // the current 'text' element, including any adjustments to the current user coordinate
+ // system due to a possible transform attribute on the current 'text' element.
+ // http://www.w3.org/TR/SVG/text.html#TextPathElement
+ pathData.transform(pathElement->animatedLocalTransform());
+ return pathData;
+}
+
+float RenderSVGTextPath::startOffset() const
+{
+ return static_cast<SVGTextPathElement*>(node())->startOffset().valueAsPercentage();
+}
+
+bool RenderSVGTextPath::exactAlignment() const
+{
+ return static_cast<SVGTextPathElement*>(node())->spacing() == SVG_TEXTPATH_SPACINGTYPE_EXACT;
+}
+
+bool RenderSVGTextPath::stretchMethod() const
+{
+ return static_cast<SVGTextPathElement*>(node())->method() == SVG_TEXTPATH_METHODTYPE_STRETCH;
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/RenderSVGTextPath.h b/Source/WebCore/rendering/svg/RenderSVGTextPath.h
new file mode 100644
index 0000000..a71edf5
--- /dev/null
+++ b/Source/WebCore/rendering/svg/RenderSVGTextPath.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ * Copyright (C) 2009 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef RenderSVGTextPath_h
+#define RenderSVGTextPath_h
+
+#if ENABLE(SVG)
+#include "RenderSVGInline.h"
+
+namespace WebCore {
+
+class RenderSVGTextPath : public RenderSVGInline {
+public:
+ RenderSVGTextPath(Node*);
+
+ Path layoutPath() const;
+ float startOffset() const;
+ bool exactAlignment() const;
+ bool stretchMethod() const;
+
+ virtual bool isSVGTextPath() const { return true; }
+
+private:
+ virtual const char* renderName() const { return "RenderSVGTextPath"; }
+
+ float m_startOffset;
+
+ bool m_exactAlignment : 1;
+ bool m_stretchMethod : 1;
+
+ Path m_layoutPath;
+};
+
+inline RenderSVGTextPath* toRenderSVGTextPath(RenderObject* object)
+{
+ ASSERT(!object || !strcmp(object->renderName(), "RenderSVGTextPath"));
+ return static_cast<RenderSVGTextPath*>(object);
+}
+
+// This will catch anyone doing an unnecessary cast.
+void toRenderSVGTextPath(const RenderSVGTextPath*);
+
+}
+
+#endif // ENABLE(SVG)
+#endif // RenderSVGTextPath_h
diff --git a/Source/WebCore/rendering/svg/SVGInlineFlowBox.cpp b/Source/WebCore/rendering/svg/SVGInlineFlowBox.cpp
new file mode 100644
index 0000000..ea806c7
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGInlineFlowBox.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * (C) 2006 Apple Computer Inc.
+ * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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"
+#include "SVGInlineFlowBox.h"
+
+#if ENABLE(SVG)
+#include "DocumentMarkerController.h"
+#include "GraphicsContext.h"
+#include "RenderSVGInlineText.h"
+#include "SVGInlineTextBox.h"
+#include "SVGRenderSupport.h"
+
+using namespace std;
+
+namespace WebCore {
+
+void SVGInlineFlowBox::paintSelectionBackground(PaintInfo& paintInfo)
+{
+ ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection);
+ ASSERT(!paintInfo.context->paintingDisabled());
+
+ PaintInfo childPaintInfo(paintInfo);
+ for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) {
+ if (child->isSVGInlineTextBox())
+ static_cast<SVGInlineTextBox*>(child)->paintSelectionBackground(childPaintInfo);
+ else if (child->isSVGInlineFlowBox())
+ static_cast<SVGInlineFlowBox*>(child)->paintSelectionBackground(childPaintInfo);
+ }
+}
+
+void SVGInlineFlowBox::paint(PaintInfo& paintInfo, int, int)
+{
+ ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection);
+ ASSERT(!paintInfo.context->paintingDisabled());
+
+ RenderObject* boxRenderer = renderer();
+ ASSERT(boxRenderer);
+
+ PaintInfo childPaintInfo(paintInfo);
+ childPaintInfo.context->save();
+
+ if (SVGRenderSupport::prepareToRenderSVGContent(boxRenderer, childPaintInfo)) {
+ for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) {
+ if (child->isSVGInlineTextBox())
+ computeTextMatchMarkerRectForRenderer(toRenderSVGInlineText(static_cast<SVGInlineTextBox*>(child)->textRenderer()));
+
+ child->paint(childPaintInfo, 0, 0);
+ }
+ }
+
+ SVGRenderSupport::finishRenderSVGContent(boxRenderer, childPaintInfo, paintInfo.context);
+ childPaintInfo.context->restore();
+}
+
+IntRect SVGInlineFlowBox::calculateBoundaries() const
+{
+ IntRect childRect;
+ for (InlineBox* child = firstChild(); child; child = child->nextOnLine())
+ childRect.unite(child->calculateBoundaries());
+ return childRect;
+}
+
+void SVGInlineFlowBox::computeTextMatchMarkerRectForRenderer(RenderSVGInlineText* textRenderer)
+{
+ ASSERT(textRenderer);
+
+ Node* node = textRenderer->node();
+ if (!node || !node->inDocument())
+ return;
+
+ RenderStyle* style = textRenderer->style();
+ ASSERT(style);
+
+ Document* document = textRenderer->document();
+ Vector<DocumentMarker> markers = document->markers()->markersForNode(textRenderer->node());
+
+ Vector<DocumentMarker>::iterator markerEnd = markers.end();
+ for (Vector<DocumentMarker>::iterator markerIt = markers.begin(); markerIt != markerEnd; ++markerIt) {
+ const DocumentMarker& marker = *markerIt;
+
+ // SVG is only interessted in the TextMatch marker, for now.
+ if (marker.type != DocumentMarker::TextMatch)
+ continue;
+
+ FloatRect markerRect;
+ for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+ ASSERT(box->isSVGInlineTextBox());
+ SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(box);
+
+ int markerStartPosition = max<int>(marker.startOffset - textBox->start(), 0);
+ int markerEndPosition = min<int>(marker.endOffset - textBox->start(), textBox->len());
+
+ if (markerStartPosition >= markerEndPosition)
+ continue;
+
+ int fragmentStartPosition = 0;
+ int fragmentEndPosition = 0;
+
+ const Vector<SVGTextFragment>& fragments = textBox->textFragments();
+ unsigned textFragmentsSize = fragments.size();
+ for (unsigned i = 0; i < textFragmentsSize; ++i) {
+ const SVGTextFragment& fragment = fragments.at(i);
+
+ fragmentStartPosition = markerStartPosition;
+ fragmentEndPosition = markerEndPosition;
+ if (!textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition))
+ continue;
+
+ FloatRect fragmentRect = textBox->selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style);
+ if (!fragment.transform.isIdentity())
+ fragmentRect = fragment.transform.mapRect(fragmentRect);
+
+ markerRect.unite(fragmentRect);
+ }
+ }
+
+ document->markers()->setRenderedRectForMarker(node, marker, textRenderer->localToAbsoluteQuad(markerRect).enclosingBoundingBox());
+ }
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/SVGInlineFlowBox.h b/Source/WebCore/rendering/svg/SVGInlineFlowBox.h
new file mode 100644
index 0000000..2358f2d
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGInlineFlowBox.h
@@ -0,0 +1,61 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * (C) 2006 Apple Computer Inc.
+ *
+ * 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.
+ *
+ */
+
+#ifndef SVGInlineFlowBox_h
+#define SVGInlineFlowBox_h
+
+#if ENABLE(SVG)
+#include "InlineFlowBox.h"
+
+namespace WebCore {
+
+class RenderSVGInlineText;
+
+class SVGInlineFlowBox : public InlineFlowBox {
+public:
+ SVGInlineFlowBox(RenderObject* obj)
+ : InlineFlowBox(obj)
+ , m_logicalHeight(0)
+ {
+ }
+
+ virtual bool isSVGInlineFlowBox() const { return true; }
+ virtual int virtualLogicalHeight() const { return m_logicalHeight; }
+ void setLogicalHeight(int h) { m_logicalHeight = h; }
+
+ void paintSelectionBackground(PaintInfo&);
+ virtual void paint(PaintInfo&, int tx, int ty);
+
+ virtual IntRect calculateBoundaries() const;
+
+ static void computeTextMatchMarkerRectForRenderer(RenderSVGInlineText*);
+
+private:
+ int m_logicalHeight;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+
+#endif // SVGInlineFlowBox_h
diff --git a/Source/WebCore/rendering/svg/SVGInlineTextBox.cpp b/Source/WebCore/rendering/svg/SVGInlineTextBox.cpp
new file mode 100644
index 0000000..5d0278b
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGInlineTextBox.cpp
@@ -0,0 +1,608 @@
+/**
+ * Copyright (C) 2007 Rob Buis <buis@kde.org>
+ * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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"
+#include "SVGInlineTextBox.h"
+
+#if ENABLE(SVG)
+#include "FloatConversion.h"
+#include "GraphicsContext.h"
+#include "InlineFlowBox.h"
+#include "RenderBlock.h"
+#include "RenderSVGInlineText.h"
+#include "RenderSVGResource.h"
+#include "RenderSVGResourceSolidColor.h"
+#include "SVGRootInlineBox.h"
+
+using namespace std;
+
+namespace WebCore {
+
+SVGInlineTextBox::SVGInlineTextBox(RenderObject* object)
+ : InlineTextBox(object)
+ , m_logicalHeight(0)
+ , m_paintingResourceMode(ApplyToDefaultMode)
+ , m_startsNewTextChunk(false)
+ , m_paintingResource(0)
+{
+}
+
+int SVGInlineTextBox::offsetForPosition(int, bool) const
+{
+ // SVG doesn't use the standard offset <-> position selection system, as it's not suitable for SVGs complex needs.
+ // vertical text selection, inline boxes spanning multiple lines (contrary to HTML, etc.)
+ ASSERT_NOT_REACHED();
+ return 0;
+}
+
+int SVGInlineTextBox::offsetForPositionInFragment(const SVGTextFragment& fragment, float position, bool includePartialGlyphs) const
+{
+ RenderText* textRenderer = this->textRenderer();
+ ASSERT(textRenderer);
+
+ RenderStyle* style = textRenderer->style();
+ ASSERT(style);
+
+ TextRun textRun(constructTextRun(style, fragment));
+
+ // Eventually handle lengthAdjust="spacingAndGlyphs".
+ // FIXME: Handle vertical text.
+ if (!fragment.transform.isIdentity())
+ textRun.setHorizontalGlyphStretch(narrowPrecisionToFloat(fragment.transform.xScale()));
+
+ return fragment.positionListOffset - start() + style->font().offsetForPosition(textRun, position, includePartialGlyphs);
+}
+
+int SVGInlineTextBox::positionForOffset(int) const
+{
+ // SVG doesn't use the offset <-> position selection system.
+ ASSERT_NOT_REACHED();
+ return 0;
+}
+
+FloatRect SVGInlineTextBox::selectionRectForTextFragment(const SVGTextFragment& fragment, int startPosition, int endPosition, RenderStyle* style)
+{
+ ASSERT(startPosition < endPosition);
+
+ const Font& font = style->font();
+ FloatPoint textOrigin(fragment.x, fragment.y - font.ascent());
+ return font.selectionRectForText(constructTextRun(style, fragment), textOrigin, fragment.height, startPosition, endPosition);
+}
+
+IntRect SVGInlineTextBox::selectionRect(int, int, int startPosition, int endPosition)
+{
+ int boxStart = start();
+ startPosition = max(startPosition - boxStart, 0);
+ endPosition = min(endPosition - boxStart, static_cast<int>(len()));
+ if (startPosition >= endPosition)
+ return IntRect();
+
+ RenderText* text = textRenderer();
+ ASSERT(text);
+
+ RenderStyle* style = text->style();
+ ASSERT(style);
+
+ FloatRect selectionRect;
+ int fragmentStartPosition = 0;
+ int fragmentEndPosition = 0;
+
+ unsigned textFragmentsSize = m_textFragments.size();
+ for (unsigned i = 0; i < textFragmentsSize; ++i) {
+ const SVGTextFragment& fragment = m_textFragments.at(i);
+
+ fragmentStartPosition = startPosition;
+ fragmentEndPosition = endPosition;
+ if (!mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition))
+ continue;
+
+ FloatRect fragmentRect = selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style);
+ if (!fragment.transform.isIdentity())
+ fragmentRect = fragment.transform.mapRect(fragmentRect);
+
+ selectionRect.unite(fragmentRect);
+ }
+
+ return enclosingIntRect(selectionRect);
+}
+
+void SVGInlineTextBox::paintSelectionBackground(PaintInfo& paintInfo)
+{
+ ASSERT(paintInfo.shouldPaintWithinRoot(renderer()));
+ ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection);
+ ASSERT(truncation() == cNoTruncation);
+
+ if (renderer()->style()->visibility() != VISIBLE)
+ return;
+
+ RenderObject* parentRenderer = parent()->renderer();
+ ASSERT(parentRenderer);
+ ASSERT(!parentRenderer->document()->printing());
+
+ // Determine whether or not we're selected.
+ bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection;
+ bool hasSelection = selectionState() != RenderObject::SelectionNone;
+ if (!hasSelection || paintSelectedTextOnly)
+ return;
+
+ Color backgroundColor = renderer()->selectionBackgroundColor();
+ if (!backgroundColor.isValid() || !backgroundColor.alpha())
+ return;
+
+ RenderStyle* style = parentRenderer->style();
+ ASSERT(style);
+
+ const SVGRenderStyle* svgStyle = style->svgStyle();
+ ASSERT(svgStyle);
+
+ bool hasFill = svgStyle->hasFill();
+ bool hasStroke = svgStyle->hasStroke();
+
+ RenderStyle* selectionStyle = style;
+ if (hasSelection) {
+ selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION);
+ if (selectionStyle) {
+ const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle();
+ ASSERT(svgSelectionStyle);
+
+ if (!hasFill)
+ hasFill = svgSelectionStyle->hasFill();
+ if (!hasStroke)
+ hasStroke = svgSelectionStyle->hasStroke();
+ } else
+ selectionStyle = style;
+ }
+
+ int startPosition, endPosition;
+ selectionStartEnd(startPosition, endPosition);
+
+ int fragmentStartPosition = 0;
+ int fragmentEndPosition = 0;
+ unsigned textFragmentsSize = m_textFragments.size();
+ for (unsigned i = 0; i < textFragmentsSize; ++i) {
+ SVGTextFragment& fragment = m_textFragments.at(i);
+ ASSERT(!m_paintingResource);
+
+ fragmentStartPosition = startPosition;
+ fragmentEndPosition = endPosition;
+ if (!mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition))
+ continue;
+
+ paintInfo.context->save();
+
+ if (!fragment.transform.isIdentity())
+ paintInfo.context->concatCTM(fragment.transform);
+
+ paintInfo.context->setFillColor(backgroundColor, style->colorSpace());
+ paintInfo.context->fillRect(selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style), backgroundColor, style->colorSpace());
+
+ m_paintingResourceMode = ApplyToDefaultMode;
+ paintInfo.context->restore();
+ }
+
+ ASSERT(!m_paintingResource);
+}
+
+void SVGInlineTextBox::paint(PaintInfo& paintInfo, int, int)
+{
+ ASSERT(paintInfo.shouldPaintWithinRoot(renderer()));
+ ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection);
+ ASSERT(truncation() == cNoTruncation);
+
+ if (renderer()->style()->visibility() != VISIBLE)
+ return;
+
+ // Note: We're explicitely not supporting composition & custom underlines and custom highlighters - unlike InlineTextBox.
+ // If we ever need that for SVG, it's very easy to refactor and reuse the code.
+
+ RenderObject* parentRenderer = parent()->renderer();
+ ASSERT(parentRenderer);
+
+ bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection;
+ bool hasSelection = !parentRenderer->document()->printing() && selectionState() != RenderObject::SelectionNone;
+ if (!hasSelection && paintSelectedTextOnly)
+ return;
+
+ RenderStyle* style = parentRenderer->style();
+ ASSERT(style);
+
+ const SVGRenderStyle* svgStyle = style->svgStyle();
+ ASSERT(svgStyle);
+
+ bool hasFill = svgStyle->hasFill();
+ bool hasStroke = svgStyle->hasStroke();
+
+ RenderStyle* selectionStyle = style;
+ if (hasSelection) {
+ selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION);
+ if (selectionStyle) {
+ const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle();
+ ASSERT(svgSelectionStyle);
+
+ if (!hasFill)
+ hasFill = svgSelectionStyle->hasFill();
+ if (!hasStroke)
+ hasStroke = svgSelectionStyle->hasStroke();
+ } else
+ selectionStyle = style;
+ }
+
+ unsigned textFragmentsSize = m_textFragments.size();
+ for (unsigned i = 0; i < textFragmentsSize; ++i) {
+ SVGTextFragment& fragment = m_textFragments.at(i);
+ ASSERT(!m_paintingResource);
+
+ paintInfo.context->save();
+
+ if (!fragment.transform.isIdentity())
+ paintInfo.context->concatCTM(fragment.transform);
+
+ // Spec: All text decorations except line-through should be drawn before the text is filled and stroked; thus, the text is rendered on top of these decorations.
+ int decorations = style->textDecorationsInEffect();
+ if (decorations & UNDERLINE)
+ paintDecoration(paintInfo.context, UNDERLINE, fragment);
+ if (decorations & OVERLINE)
+ paintDecoration(paintInfo.context, OVERLINE, fragment);
+
+ // Fill text
+ if (hasFill) {
+ m_paintingResourceMode = ApplyToFillMode | ApplyToTextMode;
+ paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly);
+ }
+
+ // Stroke text
+ if (hasStroke) {
+ m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode;
+ paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly);
+ }
+
+ // Spec: Line-through should be drawn after the text is filled and stroked; thus, the line-through is rendered on top of the text.
+ if (decorations & LINE_THROUGH)
+ paintDecoration(paintInfo.context, LINE_THROUGH, fragment);
+
+ m_paintingResourceMode = ApplyToDefaultMode;
+ paintInfo.context->restore();
+ }
+
+ ASSERT(!m_paintingResource);
+}
+
+bool SVGInlineTextBox::acquirePaintingResource(GraphicsContext*& context, RenderObject* renderer, RenderStyle* style)
+{
+ ASSERT(renderer);
+ ASSERT(style);
+ ASSERT(m_paintingResourceMode != ApplyToDefaultMode);
+
+ Color fallbackColor;
+ if (m_paintingResourceMode & ApplyToFillMode)
+ m_paintingResource = RenderSVGResource::fillPaintingResource(renderer, style, fallbackColor);
+ else if (m_paintingResourceMode & ApplyToStrokeMode)
+ m_paintingResource = RenderSVGResource::strokePaintingResource(renderer, style, fallbackColor);
+ else {
+ // We're either called for stroking or filling.
+ ASSERT_NOT_REACHED();
+ }
+
+ if (!m_paintingResource)
+ return false;
+
+ if (!m_paintingResource->applyResource(renderer, style, context, m_paintingResourceMode)) {
+ if (fallbackColor.isValid()) {
+ RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource();
+ fallbackResource->setColor(fallbackColor);
+
+ m_paintingResource = fallbackResource;
+ m_paintingResource->applyResource(renderer, style, context, m_paintingResourceMode);
+ }
+ }
+
+ return true;
+}
+
+void SVGInlineTextBox::releasePaintingResource(GraphicsContext*& context, const Path* path)
+{
+ ASSERT(m_paintingResource);
+
+ RenderObject* parentRenderer = parent()->renderer();
+ ASSERT(parentRenderer);
+
+ m_paintingResource->postApplyResource(parentRenderer, context, m_paintingResourceMode, path);
+ m_paintingResource = 0;
+}
+
+bool SVGInlineTextBox::prepareGraphicsContextForTextPainting(GraphicsContext*& context, TextRun& textRun, RenderStyle* style)
+{
+ bool acquiredResource = acquirePaintingResource(context, parent()->renderer(), style);
+
+#if ENABLE(SVG_FONTS)
+ // SVG Fonts need access to the painting resource used to draw the current text chunk.
+ if (acquiredResource)
+ textRun.setActivePaintingResource(m_paintingResource);
+#endif
+
+ return acquiredResource;
+}
+
+void SVGInlineTextBox::restoreGraphicsContextAfterTextPainting(GraphicsContext*& context, TextRun& textRun)
+{
+ releasePaintingResource(context, /* path */0);
+
+#if ENABLE(SVG_FONTS)
+ textRun.setActivePaintingResource(0);
+#endif
+}
+
+TextRun SVGInlineTextBox::constructTextRun(RenderStyle* style, const SVGTextFragment& fragment) const
+{
+ ASSERT(style);
+ ASSERT(textRenderer());
+
+ RenderText* text = textRenderer();
+ ASSERT(text);
+
+ TextRun run(text->characters() + fragment.positionListOffset
+ , fragment.length
+ , false /* allowTabs */
+ , 0 /* xPos, only relevant with allowTabs=true */
+ , 0 /* padding, only relevant for justified text, not relevant for SVG */
+ , direction() == RTL
+ , m_dirOverride || style->visuallyOrdered() /* directionalOverride */);
+
+#if ENABLE(SVG_FONTS)
+ RenderObject* parentRenderer = parent()->renderer();
+ ASSERT(parentRenderer);
+
+ run.setReferencingRenderObject(parentRenderer);
+#endif
+
+ // Disable any word/character rounding.
+ run.disableRoundingHacks();
+
+ // We handle letter & word spacing ourselves.
+ run.disableSpacing();
+ return run;
+}
+
+bool SVGInlineTextBox::mapStartEndPositionsIntoFragmentCoordinates(const SVGTextFragment& fragment, int& startPosition, int& endPosition) const
+{
+ if (startPosition >= endPosition)
+ return false;
+
+ int offset = static_cast<int>(fragment.positionListOffset) - start();
+ int length = static_cast<int>(fragment.length);
+
+ if (startPosition >= offset + length || endPosition <= offset)
+ return false;
+
+ if (startPosition < offset)
+ startPosition = 0;
+ else
+ startPosition -= offset;
+
+ if (endPosition > offset + length)
+ endPosition = length;
+ else {
+ ASSERT(endPosition >= offset);
+ endPosition -= offset;
+ }
+
+ ASSERT(startPosition < endPosition);
+ return true;
+}
+
+static inline float positionOffsetForDecoration(ETextDecoration decoration, const Font& font, float thickness)
+{
+ // FIXME: For SVG Fonts we need to use the attributes defined in the <font-face> if specified.
+ // Compatible with Batik/Opera.
+ if (decoration == UNDERLINE)
+ return font.ascent() + thickness * 1.5f;
+ if (decoration == OVERLINE)
+ return thickness;
+ if (decoration == LINE_THROUGH)
+ return font.ascent() * 5.0f / 8.0f;
+
+ ASSERT_NOT_REACHED();
+ return 0.0f;
+}
+
+static inline float thicknessForDecoration(ETextDecoration, const Font& font)
+{
+ // FIXME: For SVG Fonts we need to use the attributes defined in the <font-face> if specified.
+ // Compatible with Batik/Opera
+ return font.size() / 20.0f;
+}
+
+static inline RenderObject* findRenderObjectDefininingTextDecoration(InlineFlowBox* parentBox)
+{
+ // Lookup first render object in parent hierarchy which has text-decoration set.
+ RenderObject* renderer = 0;
+ while (parentBox) {
+ renderer = parentBox->renderer();
+
+ if (renderer->style() && renderer->style()->textDecoration() != TDNONE)
+ break;
+
+ parentBox = parentBox->parent();
+ }
+
+ ASSERT(renderer);
+ return renderer;
+}
+
+void SVGInlineTextBox::paintDecoration(GraphicsContext* context, ETextDecoration decoration, const SVGTextFragment& fragment)
+{
+ if (textRenderer()->style()->textDecorationsInEffect() == TDNONE)
+ return;
+
+ // Find out which render style defined the text-decoration, as its fill/stroke properties have to be used for drawing instead of ours.
+ RenderObject* decorationRenderer = findRenderObjectDefininingTextDecoration(parent());
+ RenderStyle* decorationStyle = decorationRenderer->style();
+ ASSERT(decorationStyle);
+
+ if (decorationStyle->visibility() == HIDDEN)
+ return;
+
+ const SVGRenderStyle* svgDecorationStyle = decorationStyle->svgStyle();
+ ASSERT(svgDecorationStyle);
+
+ bool hasDecorationFill = svgDecorationStyle->hasFill();
+ bool hasDecorationStroke = svgDecorationStyle->hasStroke();
+
+ if (hasDecorationFill) {
+ m_paintingResourceMode = ApplyToFillMode;
+ paintDecorationWithStyle(context, decoration, fragment, decorationRenderer);
+ }
+
+ if (hasDecorationStroke) {
+ m_paintingResourceMode = ApplyToStrokeMode;
+ paintDecorationWithStyle(context, decoration, fragment, decorationRenderer);
+ }
+}
+
+void SVGInlineTextBox::paintDecorationWithStyle(GraphicsContext* context, ETextDecoration decoration, const SVGTextFragment& fragment, RenderObject* decorationRenderer)
+{
+ ASSERT(!m_paintingResource);
+ ASSERT(m_paintingResourceMode != ApplyToDefaultMode);
+
+ RenderStyle* decorationStyle = decorationRenderer->style();
+ ASSERT(decorationStyle);
+
+ const Font& font = decorationStyle->font();
+
+ // The initial y value refers to overline position.
+ float thickness = thicknessForDecoration(decoration, font);
+
+ if (fragment.width <= 0 && thickness <= 0)
+ return;
+
+ float y = fragment.y - font.ascent() + positionOffsetForDecoration(decoration, font, thickness);
+
+ Path path;
+ path.addRect(FloatRect(fragment.x, y, fragment.width, thickness));
+
+ context->save();
+
+ if (acquirePaintingResource(context, decorationRenderer, decorationStyle))
+ releasePaintingResource(context, &path);
+
+ context->restore();
+}
+
+void SVGInlineTextBox::paintTextWithShadows(GraphicsContext* context, RenderStyle* style, TextRun& textRun, const SVGTextFragment& fragment, int startPosition, int endPosition)
+{
+ const Font& font = style->font();
+ const ShadowData* shadow = style->textShadow();
+
+ FloatPoint textOrigin(fragment.x, fragment.y);
+ FloatRect shadowRect(FloatPoint(textOrigin.x(), textOrigin.y() - font.ascent()), FloatSize(fragment.width, fragment.height));
+
+ do {
+ if (!prepareGraphicsContextForTextPainting(context, textRun, style))
+ break;
+
+ FloatSize extraOffset;
+ if (shadow)
+ extraOffset = applyShadowToGraphicsContext(context, shadow, shadowRect, false /* stroked */, true /* opaque */, true /* horizontal */);
+
+ font.drawText(context, textRun, textOrigin + extraOffset, startPosition, endPosition);
+ restoreGraphicsContextAfterTextPainting(context, textRun);
+
+ if (!shadow)
+ break;
+
+ if (shadow->next())
+ context->restore();
+ else
+ context->clearShadow();
+
+ shadow = shadow->next();
+ } while (shadow);
+}
+
+void SVGInlineTextBox::paintText(GraphicsContext* context, RenderStyle* style, RenderStyle* selectionStyle, const SVGTextFragment& fragment, bool hasSelection, bool paintSelectedTextOnly)
+{
+ ASSERT(style);
+ ASSERT(selectionStyle);
+
+ int startPosition = 0;
+ int endPosition = 0;
+ if (hasSelection) {
+ selectionStartEnd(startPosition, endPosition);
+ hasSelection = mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition);
+ }
+
+ // Fast path if there is no selection, just draw the whole chunk part using the regular style
+ TextRun textRun(constructTextRun(style, fragment));
+ if (!hasSelection || startPosition >= endPosition) {
+ paintTextWithShadows(context, style, textRun, fragment, 0, fragment.length);
+ return;
+ }
+
+ // Eventually draw text using regular style until the start position of the selection
+ if (startPosition > 0 && !paintSelectedTextOnly)
+ paintTextWithShadows(context, style, textRun, fragment, 0, startPosition);
+
+ // Draw text using selection style from the start to the end position of the selection
+ if (style != selectionStyle)
+ SVGResourcesCache::clientStyleChanged(parent()->renderer(), StyleDifferenceRepaint, selectionStyle);
+
+ TextRun selectionTextRun(constructTextRun(selectionStyle, fragment));
+ paintTextWithShadows(context, selectionStyle, textRun, fragment, startPosition, endPosition);
+
+ if (style != selectionStyle)
+ SVGResourcesCache::clientStyleChanged(parent()->renderer(), StyleDifferenceRepaint, style);
+
+ // Eventually draw text using regular style from the end position of the selection to the end of the current chunk part
+ if (endPosition < static_cast<int>(fragment.length) && !paintSelectedTextOnly)
+ paintTextWithShadows(context, style, textRun, fragment, endPosition, fragment.length);
+}
+
+IntRect SVGInlineTextBox::calculateBoundaries() const
+{
+ FloatRect textRect;
+
+ RenderText* textRenderer = this->textRenderer();
+ ASSERT(textRenderer);
+
+ RenderStyle* style = textRenderer->style();
+ ASSERT(style);
+
+ int baseline = baselinePosition(AlphabeticBaseline);
+ int heightDifference = baseline - style->font().ascent();
+
+ unsigned textFragmentsSize = m_textFragments.size();
+ for (unsigned i = 0; i < textFragmentsSize; ++i) {
+ const SVGTextFragment& fragment = m_textFragments.at(i);
+ FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height + heightDifference);
+
+ if (!fragment.transform.isIdentity())
+ fragmentRect = fragment.transform.mapRect(fragmentRect);
+
+ textRect.unite(fragmentRect);
+ }
+
+ return enclosingIntRect(textRect);
+}
+
+} // namespace WebCore
+
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGInlineTextBox.h b/Source/WebCore/rendering/svg/SVGInlineTextBox.h
new file mode 100644
index 0000000..acc5e9f
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGInlineTextBox.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 Rob Buis <buis@kde.org>
+ * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef SVGInlineTextBox_h
+#define SVGInlineTextBox_h
+
+#if ENABLE(SVG)
+#include "InlineTextBox.h"
+#include "SVGTextLayoutEngine.h"
+
+namespace WebCore {
+
+class RenderSVGResource;
+class SVGRootInlineBox;
+
+class SVGInlineTextBox : public InlineTextBox {
+public:
+ SVGInlineTextBox(RenderObject*);
+
+ virtual bool isSVGInlineTextBox() const { return true; }
+
+ virtual int virtualLogicalHeight() const { return m_logicalHeight; }
+ void setLogicalHeight(int height) { m_logicalHeight = height; }
+
+ virtual int selectionTop() { return m_y; }
+ virtual int selectionHeight() { return m_logicalHeight; }
+ virtual int offsetForPosition(int x, bool includePartialGlyphs = true) const;
+ virtual int positionForOffset(int offset) const;
+
+ void paintSelectionBackground(PaintInfo&);
+ virtual void paint(PaintInfo&, int tx, int ty);
+ virtual IntRect selectionRect(int absx, int absy, int startPosition, int endPosition);
+
+ bool mapStartEndPositionsIntoFragmentCoordinates(const SVGTextFragment&, int& startPosition, int& endPosition) const;
+
+ virtual IntRect calculateBoundaries() const;
+
+ void clearTextFragments() { m_textFragments.clear(); }
+ Vector<SVGTextFragment>& textFragments() { return m_textFragments; }
+ const Vector<SVGTextFragment>& textFragments() const { return m_textFragments; }
+
+ bool startsNewTextChunk() const { return m_startsNewTextChunk; }
+ void setStartsNewTextChunk(bool newTextChunk) { m_startsNewTextChunk = newTextChunk; }
+
+ int offsetForPositionInFragment(const SVGTextFragment&, float position, bool includePartialGlyphs) const;
+ FloatRect selectionRectForTextFragment(const SVGTextFragment&, int fragmentStartPosition, int fragmentEndPosition, RenderStyle*);
+
+private:
+ TextRun constructTextRun(RenderStyle*, const SVGTextFragment&) const;
+
+ bool acquirePaintingResource(GraphicsContext*&, RenderObject*, RenderStyle*);
+ void releasePaintingResource(GraphicsContext*&, const Path*);
+
+ bool prepareGraphicsContextForTextPainting(GraphicsContext*&, TextRun&, RenderStyle*);
+ void restoreGraphicsContextAfterTextPainting(GraphicsContext*&, TextRun&);
+
+ void paintDecoration(GraphicsContext*, ETextDecoration, const SVGTextFragment&);
+ void paintDecorationWithStyle(GraphicsContext*, ETextDecoration, const SVGTextFragment&, RenderObject* decorationRenderer);
+ void paintTextWithShadows(GraphicsContext*, RenderStyle*, TextRun&, const SVGTextFragment&, int startPosition, int endPosition);
+ void paintText(GraphicsContext*, RenderStyle*, RenderStyle* selectionStyle, const SVGTextFragment&, bool hasSelection, bool paintSelectedTextOnly);
+
+private:
+ int m_logicalHeight;
+ int m_paintingResourceMode;
+ bool m_startsNewTextChunk : 1;
+ RenderSVGResource* m_paintingResource;
+ Vector<SVGTextFragment> m_textFragments;
+};
+
+} // namespace WebCore
+
+#endif
+#endif // SVGInlineTextBox_h
diff --git a/Source/WebCore/rendering/svg/SVGRootInlineBox.cpp b/Source/WebCore/rendering/svg/SVGRootInlineBox.cpp
new file mode 100644
index 0000000..49c76de
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGRootInlineBox.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * Copyright (C) 2006 Apple Computer Inc.
+ * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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"
+#include "SVGRootInlineBox.h"
+
+#if ENABLE(SVG)
+#include "GraphicsContext.h"
+#include "RenderBlock.h"
+#include "RenderSVGInlineText.h"
+#include "SVGInlineFlowBox.h"
+#include "SVGInlineTextBox.h"
+#include "SVGNames.h"
+#include "SVGRenderSupport.h"
+#include "SVGTextPositioningElement.h"
+
+namespace WebCore {
+
+void SVGRootInlineBox::paint(PaintInfo& paintInfo, int, int)
+{
+ ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection);
+ ASSERT(!paintInfo.context->paintingDisabled());
+
+ RenderObject* boxRenderer = renderer();
+ ASSERT(boxRenderer);
+
+ bool isPrinting = renderer()->document()->printing();
+ bool hasSelection = !isPrinting && selectionState() != RenderObject::SelectionNone;
+
+ PaintInfo childPaintInfo(paintInfo);
+ if (hasSelection) {
+ for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) {
+ if (child->isSVGInlineTextBox())
+ static_cast<SVGInlineTextBox*>(child)->paintSelectionBackground(childPaintInfo);
+ else if (child->isSVGInlineFlowBox())
+ static_cast<SVGInlineFlowBox*>(child)->paintSelectionBackground(childPaintInfo);
+ }
+ }
+
+ childPaintInfo.context->save();
+
+ if (SVGRenderSupport::prepareToRenderSVGContent(boxRenderer, childPaintInfo)) {
+ for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) {
+ if (child->isSVGInlineTextBox())
+ SVGInlineFlowBox::computeTextMatchMarkerRectForRenderer(toRenderSVGInlineText(static_cast<SVGInlineTextBox*>(child)->textRenderer()));
+
+ child->paint(childPaintInfo, 0, 0);
+ }
+ }
+
+ SVGRenderSupport::finishRenderSVGContent(boxRenderer, childPaintInfo, paintInfo.context);
+ childPaintInfo.context->restore();
+}
+
+void SVGRootInlineBox::computePerCharacterLayoutInformation()
+{
+ // Perform SVG text layout phase two (see SVGTextLayoutEngine for details).
+ SVGTextLayoutEngine characterLayout;
+ layoutCharactersInTextBoxes(this, characterLayout);
+
+ // Perform SVG text layout phase three (see SVGTextChunkBuilder for details).
+ characterLayout.finishLayout();
+
+ // Perform SVG text layout phase four
+ // Position & resize all SVGInlineText/FlowBoxes in the inline box tree, resize the root box as well as the RenderSVGText parent block.
+ layoutChildBoxes(this);
+ layoutRootBox();
+}
+
+void SVGRootInlineBox::layoutCharactersInTextBoxes(InlineFlowBox* start, SVGTextLayoutEngine& characterLayout)
+{
+ for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) {
+ if (child->isSVGInlineTextBox()) {
+ ASSERT(child->renderer());
+ ASSERT(child->renderer()->isSVGInlineText());
+
+ SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(child);
+ characterLayout.layoutInlineTextBox(textBox);
+ } else {
+ ASSERT(child->isInlineFlowBox());
+
+ // Skip generated content.
+ Node* node = child->renderer()->node();
+ if (!node)
+ continue;
+
+ SVGInlineFlowBox* flowBox = static_cast<SVGInlineFlowBox*>(child);
+ bool isTextPath = node->hasTagName(SVGNames::textPathTag);
+ if (isTextPath) {
+ // Build text chunks for all <textPath> children, using the line layout algorithm.
+ // This is needeed as text-anchor is just an additional startOffset for text paths.
+ SVGTextLayoutEngine lineLayout;
+ layoutCharactersInTextBoxes(flowBox, lineLayout);
+ characterLayout.beginTextPathLayout(child->renderer(), lineLayout);
+ }
+
+ layoutCharactersInTextBoxes(flowBox, characterLayout);
+
+ if (isTextPath)
+ characterLayout.endTextPathLayout();
+ }
+ }
+}
+
+void SVGRootInlineBox::layoutChildBoxes(InlineFlowBox* start)
+{
+ for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) {
+ if (child->isSVGInlineTextBox()) {
+ ASSERT(child->renderer());
+ ASSERT(child->renderer()->isSVGInlineText());
+
+ SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(child);
+ IntRect boxRect = textBox->calculateBoundaries();
+ textBox->setX(boxRect.x());
+ textBox->setY(boxRect.y());
+ textBox->setLogicalWidth(boxRect.width());
+ textBox->setLogicalHeight(boxRect.height());
+ } else {
+ ASSERT(child->isInlineFlowBox());
+
+ // Skip generated content.
+ if (!child->renderer()->node())
+ continue;
+
+ SVGInlineFlowBox* flowBox = static_cast<SVGInlineFlowBox*>(child);
+ layoutChildBoxes(flowBox);
+
+ IntRect boxRect = flowBox->calculateBoundaries();
+ flowBox->setX(boxRect.x());
+ flowBox->setY(boxRect.y());
+ flowBox->setLogicalWidth(boxRect.width());
+ flowBox->setLogicalHeight(boxRect.height());
+ }
+ }
+}
+
+void SVGRootInlineBox::layoutRootBox()
+{
+ RenderBlock* parentBlock = block();
+ ASSERT(parentBlock);
+
+ IntRect childRect;
+ for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) {
+ // Skip generated content.
+ if (!child->renderer()->node())
+ continue;
+ childRect.unite(child->calculateBoundaries());
+ }
+
+ int xBlock = childRect.x();
+ int yBlock = childRect.y();
+ int widthBlock = childRect.width();
+ int heightBlock = childRect.height();
+
+ // Finally, assign the root block position, now that all content is laid out.
+ parentBlock->setLocation(xBlock, yBlock);
+ parentBlock->setWidth(widthBlock);
+ parentBlock->setHeight(heightBlock);
+
+ // Position all children relative to the parent block.
+ for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) {
+ // Skip generated content.
+ if (!child->renderer()->node())
+ continue;
+ child->adjustPosition(-xBlock, -yBlock);
+ }
+
+ // Position ourselves.
+ setX(0);
+ setY(0);
+ setLogicalWidth(widthBlock);
+ setLogicalHeight(heightBlock);
+ setBlockLogicalHeight(heightBlock);
+ setLineTopBottomPositions(0, heightBlock);
+}
+
+InlineBox* SVGRootInlineBox::closestLeafChildForPosition(const IntPoint& point)
+{
+ InlineBox* firstLeaf = firstLeafChild();
+ InlineBox* lastLeaf = lastLeafChild();
+ if (firstLeaf == lastLeaf)
+ return firstLeaf;
+
+ // FIXME: Check for vertical text!
+ InlineBox* closestLeaf = 0;
+ for (InlineBox* leaf = firstLeaf; leaf; leaf = leaf->nextLeafChild()) {
+ if (point.y() < leaf->m_y)
+ continue;
+ if (point.y() > leaf->m_y + leaf->virtualLogicalHeight())
+ continue;
+
+ closestLeaf = leaf;
+ if (point.x() < leaf->m_x + leaf->m_logicalWidth)
+ return leaf;
+ }
+
+ return closestLeaf ? closestLeaf : lastLeaf;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/SVGRootInlineBox.h b/Source/WebCore/rendering/svg/SVGRootInlineBox.h
new file mode 100644
index 0000000..418c289
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGRootInlineBox.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
+ * (C) 2006 Apple Computer Inc.
+ * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef SVGRootInlineBox_h
+#define SVGRootInlineBox_h
+
+#if ENABLE(SVG)
+#include "RootInlineBox.h"
+#include "SVGRenderSupport.h"
+#include "SVGTextLayoutEngine.h"
+
+namespace WebCore {
+
+class SVGInlineTextBox;
+
+class SVGRootInlineBox : public RootInlineBox {
+public:
+ SVGRootInlineBox(RenderBlock* block)
+ : RootInlineBox(block)
+ , m_logicalHeight(0)
+ {
+ }
+
+ virtual bool isSVGRootInlineBox() const { return true; }
+
+ virtual int virtualLogicalHeight() const { return m_logicalHeight; }
+ void setLogicalHeight(int height) { m_logicalHeight = height; }
+
+ virtual void paint(PaintInfo&, int tx, int ty);
+
+ void computePerCharacterLayoutInformation();
+
+ virtual FloatRect objectBoundingBox() const { return FloatRect(); }
+ virtual FloatRect repaintRectInLocalCoordinates() const { return FloatRect(); }
+
+ InlineBox* closestLeafChildForPosition(const IntPoint&);
+
+private:
+ void layoutCharactersInTextBoxes(InlineFlowBox*, SVGTextLayoutEngine&);
+ void layoutChildBoxes(InlineFlowBox*);
+ void layoutRootBox();
+
+private:
+ int m_logicalHeight;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+
+#endif // SVGRootInlineBox_h
diff --git a/Source/WebCore/rendering/svg/SVGTextChunk.cpp b/Source/WebCore/rendering/svg/SVGTextChunk.cpp
new file mode 100644
index 0000000..5dea6ad
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextChunk.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "SVGTextChunk.h"
+
+#include "SVGInlineTextBox.h"
+#include "SVGTextFragment.h"
+
+namespace WebCore {
+
+SVGTextChunk::SVGTextChunk(bool isVerticalText, ETextAnchor textAnchor, SVGTextContentElement::SVGLengthAdjustType lengthAdjust, float desiredTextLength)
+ : m_isVerticalText(isVerticalText)
+ , m_textAnchor(textAnchor)
+ , m_lengthAdjust(lengthAdjust)
+ , m_desiredTextLength(desiredTextLength)
+{
+}
+
+void SVGTextChunk::calculateLength(float& length, unsigned& characters) const
+{
+ SVGTextFragment* lastFragment = 0;
+
+ unsigned boxCount = m_boxes.size();
+ for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
+ SVGInlineTextBox* textBox = m_boxes.at(boxPosition);
+ Vector<SVGTextFragment>& fragments = textBox->textFragments();
+
+ unsigned size = fragments.size();
+ if (!size)
+ continue;
+
+ for (unsigned i = 0; i < size; ++i) {
+ SVGTextFragment& fragment = fragments.at(i);
+ characters += fragment.length;
+
+ if (m_isVerticalText)
+ length += fragment.height;
+ else
+ length += fragment.width;
+
+ if (!lastFragment) {
+ lastFragment = &fragment;
+ continue;
+ }
+
+ // Resepect gap between chunks.
+ if (m_isVerticalText)
+ length += fragment.y - (lastFragment->y + lastFragment->height);
+ else
+ length += fragment.x - (lastFragment->x + lastFragment->width);
+
+ lastFragment = &fragment;
+ }
+ }
+}
+
+float SVGTextChunk::calculateTextAnchorShift(float length) const
+{
+ switch (m_textAnchor) {
+ case TA_START:
+ return 0;
+ case TA_MIDDLE:
+ return -length / 2;
+ case TA_END:
+ return -length;
+ };
+
+ ASSERT_NOT_REACHED();
+ return 0;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/SVGTextChunk.h b/Source/WebCore/rendering/svg/SVGTextChunk.h
new file mode 100644
index 0000000..ebe6d81
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextChunk.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef SVGTextChunk_h
+#define SVGTextChunk_h
+
+#if ENABLE(SVG)
+#include "SVGRenderStyleDefs.h"
+#include "SVGTextContentElement.h"
+
+namespace WebCore {
+
+class SVGInlineTextBox;
+
+// A SVGTextChunk describes a range of SVGTextFragments, see the SVG spec definition of a "text chunk".
+class SVGTextChunk {
+public:
+ SVGTextChunk(bool isVerticalText, ETextAnchor, SVGTextContentElement::SVGLengthAdjustType, float desiredTextLength);
+
+ void calculateLength(float& length, unsigned& characters) const;
+ float calculateTextAnchorShift(float length) const;
+
+ bool isVerticalText() const { return m_isVerticalText; }
+ ETextAnchor textAnchor() const { return m_textAnchor; }
+ SVGTextContentElement::SVGLengthAdjustType lengthAdjust() const { return m_lengthAdjust; }
+ float desiredTextLength() const { return m_desiredTextLength; }
+
+ Vector<SVGInlineTextBox*>& boxes() { return m_boxes; }
+ const Vector<SVGInlineTextBox*>& boxes() const { return m_boxes; }
+
+ bool hasDesiredTextLength() const { return m_lengthAdjust != SVGTextContentElement::LENGTHADJUST_UNKNOWN && m_desiredTextLength > 0; }
+ bool hasTextAnchor() const { return m_textAnchor != TA_START; }
+
+private:
+ // Contains all SVGInlineTextBoxes this chunk spans.
+ Vector<SVGInlineTextBox*> m_boxes;
+
+ // writing-mode specific property.
+ bool m_isVerticalText;
+
+ // text-anchor specific properties.
+ ETextAnchor m_textAnchor;
+
+ // textLength/lengthAdjust specific properties.
+ SVGTextContentElement::SVGLengthAdjustType m_lengthAdjust;
+ float m_desiredTextLength;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp
new file mode 100644
index 0000000..bbbae6c
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "SVGTextChunkBuilder.h"
+
+#include "RenderSVGInlineText.h"
+#include "SVGElement.h"
+#include "SVGInlineTextBox.h"
+
+namespace WebCore {
+
+SVGTextChunkBuilder::SVGTextChunkBuilder()
+{
+}
+
+void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const
+{
+ DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ());
+ if (!m_textBoxTransformations.contains(textBox)) {
+ transform = s_identityTransform;
+ return;
+ }
+
+ transform = m_textBoxTransformations.get(textBox);
+}
+
+void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
+{
+ if (lineLayoutBoxes.isEmpty())
+ return;
+
+ bool foundStart = false;
+ unsigned lastChunkStartPosition = 0;
+ unsigned boxPosition = 0;
+ unsigned boxCount = lineLayoutBoxes.size();
+ for (; boxPosition < boxCount; ++boxPosition) {
+ SVGInlineTextBox* textBox = lineLayoutBoxes.at(boxPosition);
+ if (!textBox->startsNewTextChunk())
+ continue;
+
+ if (!foundStart) {
+ lastChunkStartPosition = boxPosition;
+ foundStart = true;
+ } else {
+ ASSERT(boxPosition > lastChunkStartPosition);
+ addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
+ lastChunkStartPosition = boxPosition;
+ }
+ }
+
+ if (!foundStart)
+ return;
+
+ if (boxPosition - lastChunkStartPosition > 0)
+ addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
+}
+
+void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
+{
+ buildTextChunks(lineLayoutBoxes);
+ if (m_textChunks.isEmpty())
+ return;
+
+ unsigned chunkCount = m_textChunks.size();
+ for (unsigned i = 0; i < chunkCount; ++i)
+ processTextChunk(m_textChunks.at(i));
+
+ m_textChunks.clear();
+}
+
+void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount)
+{
+ SVGInlineTextBox* textBox = lineLayoutBoxes.at(boxStart);
+ ASSERT(textBox);
+
+ RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer());
+ ASSERT(textRenderer);
+
+ const RenderStyle* style = textRenderer->style();
+ ASSERT(style);
+
+ const SVGRenderStyle* svgStyle = style->svgStyle();
+ ASSERT(svgStyle);
+
+ SVGTextContentElement::SVGLengthAdjustType lengthAdjust = SVGTextContentElement::LENGTHADJUST_UNKNOWN;
+ float desiredTextLength = 0;
+
+ if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer->parent())) {
+ lengthAdjust = static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust());
+ desiredTextLength = textContentElement->textLength().value(textContentElement);
+ }
+
+ SVGTextChunk chunk(svgStyle->isVerticalWritingMode(), svgStyle->textAnchor(), lengthAdjust, desiredTextLength);
+
+ Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
+ for (unsigned i = boxStart; i < boxStart + boxCount; ++i)
+ boxes.append(lineLayoutBoxes.at(i));
+
+ m_textChunks.append(chunk);
+}
+
+void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk)
+{
+ bool processTextLength = chunk.hasDesiredTextLength();
+ bool processTextAnchor = chunk.hasTextAnchor();
+ if (!processTextAnchor && !processTextLength)
+ return;
+
+ const Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
+ unsigned boxCount = boxes.size();
+ if (!boxCount)
+ return;
+
+ // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes).
+ float chunkLength = 0;
+ unsigned chunkCharacters = 0;
+ chunk.calculateLength(chunkLength, chunkCharacters);
+
+ bool isVerticalText = chunk.isVerticalText();
+ if (processTextLength) {
+ if (chunk.lengthAdjust() == SVGTextContentElement::LENGTHADJUST_SPACING) {
+ float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters;
+ unsigned atCharacter = 0;
+ for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
+ Vector<SVGTextFragment>& fragments = boxes.at(boxPosition)->textFragments();
+ if (fragments.isEmpty())
+ continue;
+ processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter);
+ }
+ } else {
+ ASSERT(chunk.lengthAdjust() == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS);
+ float scale = chunk.desiredTextLength() / chunkLength;
+ AffineTransform spacingAndGlyphsTransform;
+
+ bool foundFirstFragment = false;
+ for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
+ SVGInlineTextBox* textBox = boxes.at(boxPosition);
+ Vector<SVGTextFragment>& fragments = textBox->textFragments();
+ if (fragments.isEmpty())
+ continue;
+
+ if (!foundFirstFragment) {
+ foundFirstFragment = true;
+ buildSpacingAndGlyphsTransform(isVerticalText, scale, fragments.first(), spacingAndGlyphsTransform);
+ }
+
+ m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform);
+ }
+ }
+ }
+
+ if (!processTextAnchor)
+ return;
+
+ // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift.
+ if (processTextLength && chunk.lengthAdjust() == SVGTextContentElement::LENGTHADJUST_SPACING) {
+ chunkLength = 0;
+ chunkCharacters = 0;
+ chunk.calculateLength(chunkLength, chunkCharacters);
+ }
+
+ float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength);
+ for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
+ Vector<SVGTextFragment>& fragments = boxes.at(boxPosition)->textFragments();
+ if (fragments.isEmpty())
+ continue;
+ processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments);
+ }
+}
+
+void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter)
+{
+ unsigned fragmentCount = fragments.size();
+ for (unsigned i = 0; i < fragmentCount; ++i) {
+ SVGTextFragment& fragment = fragments.at(i);
+
+ if (isVerticalText)
+ fragment.y += textLengthShift * atCharacter;
+ else
+ fragment.x += textLengthShift * atCharacter;
+
+ atCharacter += fragment.length;
+ }
+}
+
+void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments)
+{
+ unsigned fragmentCount = fragments.size();
+ for (unsigned i = 0; i < fragmentCount; ++i) {
+ SVGTextFragment& fragment = fragments.at(i);
+
+ if (isVerticalText)
+ fragment.y += textAnchorShift;
+ else
+ fragment.x += textAnchorShift;
+ }
+}
+
+void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform)
+{
+ spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
+
+ if (isVerticalText)
+ spacingAndGlyphsTransform.scaleNonUniform(1, scale);
+ else
+ spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
+
+ spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h
new file mode 100644
index 0000000..36342e7
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef SVGTextChunkBuilder_h
+#define SVGTextChunkBuilder_h
+
+#if ENABLE(SVG)
+#include "SVGTextChunk.h"
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+class SVGInlineTextBox;
+struct SVGTextFragment;
+
+// SVGTextChunkBuilder performs the third layout phase for SVG text.
+//
+// Phase one built the layout information from the SVG DOM stored in the RenderSVGInlineText objects (SVGTextLayoutAttributes).
+// Phase two performed the actual per-character layout, computing the final positions for each character, stored in the SVGInlineTextBox objects (SVGTextFragment).
+// Phase three performs all modifications that have to be applied to each individual text chunk (text-anchor & textLength).
+
+class SVGTextChunkBuilder : public Noncopyable {
+public:
+ SVGTextChunkBuilder();
+
+ const Vector<SVGTextChunk>& textChunks() const { return m_textChunks; }
+ void transformationForTextBox(SVGInlineTextBox*, AffineTransform&) const;
+
+ void buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes);
+ void layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes);
+
+private:
+ void addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxPosition, unsigned boxCount);
+ void processTextChunk(const SVGTextChunk&);
+
+ void processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>&, unsigned& atCharacter);
+ void processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>&);
+ void buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment&, AffineTransform&);
+
+private:
+ Vector<SVGTextChunk> m_textChunks;
+ HashMap<SVGInlineTextBox*, AffineTransform> m_textBoxTransformations;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGTextFragment.h b/Source/WebCore/rendering/svg/SVGTextFragment.h
new file mode 100644
index 0000000..2e520da
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextFragment.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef SVGTextFragment_h
+#define SVGTextFragment_h
+
+#if ENABLE(SVG)
+#include "AffineTransform.h"
+
+namespace WebCore {
+
+// A SVGTextFragment describes a text fragment of a RenderSVGInlineText which can be rendered at once.
+struct SVGTextFragment {
+ SVGTextFragment()
+ : positionListOffset(0)
+ , length(0)
+ , x(0)
+ , y(0)
+ , width(0)
+ , height(0)
+ {
+ }
+
+ // The first rendered character starts at RenderSVGInlineText::characters() + positionListOffset.
+ unsigned positionListOffset;
+ unsigned length;
+
+ float x;
+ float y;
+ float width;
+ float height;
+
+ // Includes rotation/glyph-orientation-(horizontal|vertical) transforms, lengthAdjust="spacingAndGlyphs" (for textPath only),
+ // as well as orientation related shifts (see SVGTextLayoutEngine, which builds this transformation).
+ AffineTransform transform;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp
new file mode 100644
index 0000000..3037b77
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "SVGTextLayoutAttributes.h"
+
+#include <stdio.h>
+#include <wtf/text/CString.h>
+
+namespace WebCore {
+
+SVGTextLayoutAttributes::SVGTextLayoutAttributes()
+{
+}
+
+void SVGTextLayoutAttributes::reserveCapacity(unsigned length)
+{
+ m_xValues.reserveCapacity(length);
+ m_yValues.reserveCapacity(length);
+ m_dxValues.reserveCapacity(length);
+ m_dyValues.reserveCapacity(length);
+ m_rotateValues.reserveCapacity(length);
+}
+
+float SVGTextLayoutAttributes::emptyValue()
+{
+ static float s_emptyValue = std::numeric_limits<float>::max() - 1;
+ return s_emptyValue;
+}
+
+static inline void dumpLayoutVector(const Vector<float>& values)
+{
+ if (values.isEmpty()) {
+ fprintf(stderr, "empty");
+ return;
+ }
+
+ unsigned size = values.size();
+ for (unsigned i = 0; i < size; ++i) {
+ float value = values.at(i);
+ if (value == SVGTextLayoutAttributes::emptyValue())
+ fprintf(stderr, "x ");
+ else
+ fprintf(stderr, "%lf ", value);
+ }
+}
+
+void SVGTextLayoutAttributes::dump() const
+{
+ fprintf(stderr, "x values: ");
+ dumpLayoutVector(m_xValues);
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "y values: ");
+ dumpLayoutVector(m_yValues);
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "dx values: ");
+ dumpLayoutVector(m_dxValues);
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "dy values: ");
+ dumpLayoutVector(m_dyValues);
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "rotate values: ");
+ dumpLayoutVector(m_rotateValues);
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "character data values:\n");
+ unsigned textMetricsSize = m_textMetricsValues.size();
+ for (unsigned i = 0; i < textMetricsSize; ++i) {
+ const SVGTextMetrics& metrics = m_textMetricsValues.at(i);
+ fprintf(stderr, "| {length=%i, glyphName='%s', unicodeString='%s', width=%lf, height=%lf}\n",
+ metrics.length(), metrics.glyph().name.utf8().data(), metrics.glyph().unicodeString.utf8().data(), metrics.width(), metrics.height());
+ }
+ fprintf(stderr, "\n");
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h
new file mode 100644
index 0000000..d08d5b7
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef SVGTextLayoutAttributes_h
+#define SVGTextLayoutAttributes_h
+
+#if ENABLE(SVG)
+#include "SVGTextMetrics.h"
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class SVGTextLayoutAttributes {
+public:
+ SVGTextLayoutAttributes();
+
+ void reserveCapacity(unsigned length);
+ void dump() const;
+
+ static float emptyValue();
+
+ Vector<float>& xValues() { return m_xValues; }
+ const Vector<float>& xValues() const { return m_xValues; }
+
+ Vector<float>& yValues() { return m_yValues; }
+ const Vector<float>& yValues() const { return m_yValues; }
+
+ Vector<float>& dxValues() { return m_dxValues; }
+ const Vector<float>& dxValues() const { return m_dxValues; }
+
+ Vector<float>& dyValues() { return m_dyValues; }
+ const Vector<float>& dyValues() const { return m_dyValues; }
+
+ Vector<float>& rotateValues() { return m_rotateValues; }
+ const Vector<float>& rotateValues() const { return m_rotateValues; }
+
+ Vector<SVGTextMetrics>& textMetricsValues() { return m_textMetricsValues; }
+ const Vector<SVGTextMetrics>& textMetricsValues() const { return m_textMetricsValues; }
+
+private:
+ Vector<float> m_xValues;
+ Vector<float> m_yValues;
+ Vector<float> m_dxValues;
+ Vector<float> m_dyValues;
+ Vector<float> m_rotateValues;
+ Vector<SVGTextMetrics> m_textMetricsValues;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp
new file mode 100644
index 0000000..3122912
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "SVGTextLayoutAttributesBuilder.h"
+
+#include "RenderSVGInlineText.h"
+#include "RenderSVGText.h"
+#include "SVGTextPositioningElement.h"
+
+// Set to a value > 0 to dump the text layout attributes
+#define DUMP_TEXT_LAYOUT_ATTRIBUTES 0
+
+namespace WebCore {
+
+SVGTextLayoutAttributesBuilder::SVGTextLayoutAttributesBuilder()
+{
+}
+
+void SVGTextLayoutAttributesBuilder::buildLayoutAttributesForTextSubtree(RenderSVGText* textRoot)
+{
+ ASSERT(textRoot);
+ m_scopes.clear();
+
+ // Build list of x/y/dx/dy/rotate values for each subtree element that may define these values (tspan/textPath etc).
+ unsigned atCharacter = 0;
+ UChar lastCharacter = '\0';
+ buildLayoutScopes(textRoot, atCharacter, lastCharacter);
+
+ if (!atCharacter)
+ return;
+
+ // Build list of x/y/dx/dy/rotate values for the outermost <text> element.
+ buildOutermostLayoutScope(textRoot, atCharacter);
+
+ // Propagate layout attributes to each RenderSVGInlineText object.
+ atCharacter = 0;
+ lastCharacter = '\0';
+ propagateLayoutAttributes(textRoot, atCharacter, lastCharacter);
+}
+
+static inline void extractFloatValuesFromSVGLengthList(SVGElement* lengthContext, const SVGLengthList& list, Vector<float>& floatValues, unsigned textContentLength)
+{
+ ASSERT(lengthContext);
+
+ unsigned length = list.size();
+ if (length > textContentLength)
+ length = textContentLength;
+ floatValues.reserveCapacity(length);
+
+ for (unsigned i = 0; i < length; ++i) {
+ const SVGLength& length = list.at(i);
+ floatValues.append(length.value(lengthContext));
+ }
+}
+
+static inline void extractFloatValuesFromSVGNumberList(const SVGNumberList& list, Vector<float>& floatValues, unsigned textContentLength)
+{
+ unsigned length = list.size();
+ if (length > textContentLength)
+ length = textContentLength;
+ floatValues.reserveCapacity(length);
+
+ for (unsigned i = 0; i < length; ++i)
+ floatValues.append(list.at(i));
+}
+
+void SVGTextLayoutAttributesBuilder::buildLayoutScope(LayoutScope& scope, RenderObject* renderer, unsigned textContentStart, unsigned textContentLength) const
+{
+ ASSERT(renderer);
+ ASSERT(renderer->style());
+
+ scope.textContentStart = textContentStart;
+ scope.textContentLength = textContentLength;
+
+ SVGTextPositioningElement* element = SVGTextPositioningElement::elementFromRenderer(renderer);
+ if (!element)
+ return;
+
+ SVGTextLayoutAttributes& attributes = scope.attributes;
+ extractFloatValuesFromSVGLengthList(element, element->x(), attributes.xValues(), textContentLength);
+ extractFloatValuesFromSVGLengthList(element, element->y(), attributes.yValues(), textContentLength);
+ extractFloatValuesFromSVGLengthList(element, element->dx(), attributes.dxValues(), textContentLength);
+ extractFloatValuesFromSVGLengthList(element, element->dy(), attributes.dyValues(), textContentLength);
+ extractFloatValuesFromSVGNumberList(element->rotate(), attributes.rotateValues(), textContentLength);
+
+ // The last rotation value spans the whole scope.
+ Vector<float>& rotateValues = attributes.rotateValues();
+ if (rotateValues.isEmpty())
+ return;
+
+ unsigned rotateValuesSize = rotateValues.size();
+ if (rotateValuesSize == textContentLength)
+ return;
+
+ float lastRotation = rotateValues.last();
+
+ rotateValues.resize(textContentLength);
+ for (unsigned i = rotateValuesSize; i < textContentLength; ++i)
+ rotateValues.at(i) = lastRotation;
+}
+
+static inline bool characterIsSpace(const UChar& character)
+{
+ return character == ' ';
+}
+
+static inline bool characterIsSpaceOrNull(const UChar& character)
+{
+ return character == ' ' || character == '\0';
+}
+
+static inline bool shouldPreserveAllWhiteSpace(RenderStyle* style)
+{
+ ASSERT(style);
+ return style->whiteSpace() == PRE;
+}
+
+void SVGTextLayoutAttributesBuilder::buildLayoutScopes(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter)
+{
+ for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) {
+ if (child->isSVGInlineText()) {
+ RenderSVGInlineText* text = toRenderSVGInlineText(child);
+
+ if (!shouldPreserveAllWhiteSpace(text->style())) {
+ const UChar* characters = text->characters();
+ unsigned textLength = text->textLength();
+ for (unsigned textPosition = 0; textPosition < textLength; ++textPosition) {
+ const UChar& currentCharacter = characters[textPosition];
+ if (characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter))
+ continue;
+
+ lastCharacter = currentCharacter;
+ ++atCharacter;
+ }
+ } else
+ atCharacter += text->textLength();
+
+ continue;
+ }
+
+ if (!child->isSVGInline())
+ continue;
+
+ unsigned textContentStart = atCharacter;
+ buildLayoutScopes(child, atCharacter, lastCharacter);
+
+ LayoutScope scope;
+ buildLayoutScope(scope, child, textContentStart, atCharacter - textContentStart);
+ m_scopes.append(scope);
+ }
+}
+
+void SVGTextLayoutAttributesBuilder::buildOutermostLayoutScope(RenderSVGText* textRoot, unsigned textLength)
+{
+ LayoutScope scope;
+ buildLayoutScope(scope, textRoot, 0, textLength);
+
+ // Handle <text> x/y default attributes.
+ Vector<float>& xValues = scope.attributes.xValues();
+ if (xValues.isEmpty())
+ xValues.append(0);
+
+ Vector<float>& yValues = scope.attributes.yValues();
+ if (yValues.isEmpty())
+ yValues.append(0);
+
+ m_scopes.prepend(scope);
+}
+
+void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter) const
+{
+ for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) {
+ if (child->isSVGInlineText()) {
+ RenderSVGInlineText* text = toRenderSVGInlineText(child);
+ const UChar* characters = text->characters();
+ unsigned textLength = text->textLength();
+ bool preserveWhiteSpace = shouldPreserveAllWhiteSpace(text->style());
+
+ SVGTextLayoutAttributes attributes;
+ attributes.reserveCapacity(textLength);
+
+ unsigned valueListPosition = atCharacter;
+ unsigned metricsLength = 1;
+ for (unsigned textPosition = 0; textPosition < textLength; textPosition += metricsLength) {
+ const UChar& currentCharacter = characters[textPosition];
+
+ SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(text, textPosition, 1);
+ metricsLength = metrics.length();
+
+ if (!preserveWhiteSpace && characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter)) {
+ assignEmptyLayoutAttributesForCharacter(attributes);
+ attributes.textMetricsValues().append(SVGTextMetrics::emptyMetrics());
+ continue;
+ }
+
+ assignLayoutAttributesForCharacter(attributes, metrics, valueListPosition);
+
+ if (metricsLength > 1) {
+ for (unsigned i = 0; i < metricsLength - 1; ++i)
+ assignEmptyLayoutAttributesForCharacter(attributes);
+ }
+
+ lastCharacter = currentCharacter;
+ valueListPosition += metricsLength;
+ }
+
+#if DUMP_TEXT_LAYOUT_ATTRIBUTES > 0
+ fprintf(stderr, "\nDumping layout attributes for RenderSVGInlineText, renderer=%p, node=%p (atCharacter: %i)\n", text, text->node(), atCharacter);
+ attributes.dump();
+#endif
+
+ text->storeLayoutAttributes(attributes);
+ atCharacter = valueListPosition;
+ continue;
+ }
+
+ if (!child->isSVGInline())
+ continue;
+
+ propagateLayoutAttributes(child, atCharacter, lastCharacter);
+ }
+}
+
+float SVGTextLayoutAttributesBuilder::nextLayoutValue(LayoutValueType type, unsigned atCharacter) const
+{
+ for (int i = m_scopes.size() - 1; i >= 0; --i) {
+ const LayoutScope& scope = m_scopes.at(i);
+ if (scope.textContentStart > atCharacter || scope.textContentStart + scope.textContentLength < atCharacter)
+ continue;
+
+ const Vector<float>* valuesPointer = 0;
+ switch (type) {
+ case XValueAttribute:
+ valuesPointer = &scope.attributes.xValues();
+ break;
+ case YValueAttribute:
+ valuesPointer = &scope.attributes.yValues();
+ break;
+ case DxValueAttribute:
+ valuesPointer = &scope.attributes.dxValues();
+ break;
+ case DyValueAttribute:
+ valuesPointer = &scope.attributes.dyValues();
+ break;
+ case RotateValueAttribute:
+ valuesPointer = &scope.attributes.rotateValues();
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ ASSERT(valuesPointer);
+ const Vector<float>& values = *valuesPointer;
+ if (values.isEmpty())
+ continue;
+
+ unsigned position = atCharacter - scope.textContentStart;
+ if (position >= values.size())
+ continue;
+
+ return values.at(position);
+ }
+
+ return SVGTextLayoutAttributes::emptyValue();
+}
+
+void SVGTextLayoutAttributesBuilder::assignLayoutAttributesForCharacter(SVGTextLayoutAttributes& attributes, SVGTextMetrics& metrics, unsigned valueListPosition) const
+{
+ attributes.xValues().append(nextLayoutValue(XValueAttribute, valueListPosition));
+ attributes.yValues().append(nextLayoutValue(YValueAttribute, valueListPosition));
+ attributes.dxValues().append(nextLayoutValue(DxValueAttribute, valueListPosition));
+ attributes.dyValues().append(nextLayoutValue(DyValueAttribute, valueListPosition));
+ attributes.rotateValues().append(nextLayoutValue(RotateValueAttribute, valueListPosition));
+ attributes.textMetricsValues().append(metrics);
+}
+
+void SVGTextLayoutAttributesBuilder::assignEmptyLayoutAttributesForCharacter(SVGTextLayoutAttributes& attributes) const
+{
+ attributes.xValues().append(SVGTextLayoutAttributes::emptyValue());
+ attributes.yValues().append(SVGTextLayoutAttributes::emptyValue());
+ attributes.dxValues().append(SVGTextLayoutAttributes::emptyValue());
+ attributes.dyValues().append(SVGTextLayoutAttributes::emptyValue());
+ attributes.rotateValues().append(SVGTextLayoutAttributes::emptyValue());
+ // This doesn't add an empty value to textMetricsValues() on purpose!
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h
new file mode 100644
index 0000000..f29ac64
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef SVGTextLayoutAttributesBuilder_h
+#define SVGTextLayoutAttributesBuilder_h
+
+#if ENABLE(SVG)
+#include "SVGTextLayoutAttributes.h"
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+class RenderObject;
+class RenderSVGText;
+
+// SVGTextLayoutAttributesBuilder performs the first layout phase for SVG text.
+//
+// It extracts the x/y/dx/dy/rotate values from the SVGTextPositioningElements in the DOM,
+// measures all characters in the RenderSVGText subtree and extracts kerning/ligature information.
+// These values are propagated to the corresponding RenderSVGInlineText renderers.
+// The first layout phase only extracts the relevant information needed in RenderBlockLineLayout
+// to create the InlineBox tree based on text chunk boundaries & BiDi information.
+// The second layout phase is carried out by SVGTextLayoutEngine.
+
+class SVGTextLayoutAttributesBuilder : public Noncopyable {
+public:
+ SVGTextLayoutAttributesBuilder();
+ void buildLayoutAttributesForTextSubtree(RenderSVGText*);
+
+private:
+ struct LayoutScope {
+ LayoutScope()
+ : textContentStart(0)
+ , textContentLength(0)
+ {
+ }
+
+ unsigned textContentStart;
+ unsigned textContentLength;
+ SVGTextLayoutAttributes attributes;
+ };
+
+ void buildLayoutScope(LayoutScope&, RenderObject*, unsigned textContentStart, unsigned textContentLength) const;
+ void buildLayoutScopes(RenderObject*, unsigned& atCharacter, UChar& lastCharacter);
+ void buildOutermostLayoutScope(RenderSVGText*, unsigned textLength);
+ void propagateLayoutAttributes(RenderObject*, unsigned& atCharacter, UChar& lastCharacter) const;
+
+ enum LayoutValueType {
+ XValueAttribute,
+ YValueAttribute,
+ DxValueAttribute,
+ DyValueAttribute,
+ RotateValueAttribute
+ };
+
+ float nextLayoutValue(LayoutValueType, unsigned atCharacter) const;
+ void assignLayoutAttributesForCharacter(SVGTextLayoutAttributes&, SVGTextMetrics&, unsigned valueListPosition) const;
+ void assignEmptyLayoutAttributesForCharacter(SVGTextLayoutAttributes&) const;
+
+private:
+ Vector<LayoutScope> m_scopes;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp
new file mode 100644
index 0000000..7eefad6
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp
@@ -0,0 +1,579 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "SVGTextLayoutEngine.h"
+
+#include "RenderSVGInlineText.h"
+#include "RenderSVGTextPath.h"
+#include "SVGElement.h"
+#include "SVGInlineTextBox.h"
+#include "SVGTextLayoutEngineBaseline.h"
+#include "SVGTextLayoutEngineSpacing.h"
+
+// Set to a value > 0 to dump the text fragments
+#define DUMP_TEXT_FRAGMENTS 0
+
+namespace WebCore {
+
+SVGTextLayoutEngine::SVGTextLayoutEngine()
+ : m_x(0)
+ , m_y(0)
+ , m_dx(0)
+ , m_dy(0)
+ , m_isVerticalText(false)
+ , m_inPathLayout(false)
+ , m_textPathLength(0)
+ , m_textPathCurrentOffset(0)
+ , m_textPathSpacing(0)
+ , m_textPathScaling(1)
+{
+}
+
+void SVGTextLayoutEngine::updateCharacerPositionIfNeeded(float& x, float& y)
+{
+ if (m_inPathLayout)
+ return;
+
+ // Replace characters x/y position, with the current text position plus any
+ // relative adjustments, if it doesn't specify an absolute position itself.
+ if (x == SVGTextLayoutAttributes::emptyValue())
+ x = m_x + m_dx;
+
+ if (y == SVGTextLayoutAttributes::emptyValue())
+ y = m_y + m_dy;
+
+ m_dx = 0;
+ m_dy = 0;
+}
+
+void SVGTextLayoutEngine::updateCurrentTextPosition(float x, float y, float glyphAdvance)
+{
+ // Update current text position after processing the character.
+ if (m_isVerticalText) {
+ m_x = x;
+ m_y = y + glyphAdvance;
+ } else {
+ m_x = x + glyphAdvance;
+ m_y = y;
+ }
+}
+
+void SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded(Vector<float>& dxValues, Vector<float>& dyValues, unsigned positionListOffset)
+{
+ // Update relative positioning information.
+ if (dxValues.isEmpty() && dyValues.isEmpty())
+ return;
+
+ float dx = 0;
+ if (!dxValues.isEmpty()) {
+ float& dxCurrent = dxValues.at(positionListOffset);
+ if (dxCurrent != SVGTextLayoutAttributes::emptyValue())
+ dx = dxCurrent;
+ }
+
+ float dy = 0;
+ if (!dyValues.isEmpty()) {
+ float& dyCurrent = dyValues.at(positionListOffset);
+ if (dyCurrent != SVGTextLayoutAttributes::emptyValue())
+ dy = dyCurrent;
+ }
+
+ if (m_inPathLayout) {
+ if (m_isVerticalText) {
+ m_dx += dx;
+ m_dy = dy;
+ } else {
+ m_dx = dx;
+ m_dy += dy;
+ }
+
+ return;
+ }
+
+ m_dx = dx;
+ m_dy = dy;
+}
+
+void SVGTextLayoutEngine::recordTextFragment(SVGInlineTextBox* textBox, RenderSVGInlineText* text, unsigned positionListOffset, const SVGTextMetrics& lastCharacterMetrics)
+{
+ ASSERT(!m_currentTextFragment.length);
+
+ // Figure out length of fragment.
+ m_currentTextFragment.length = positionListOffset - m_currentTextFragment.positionListOffset;
+
+ // Figure out fragment metrics.
+ if (m_currentTextFragment.length == 1) {
+ // Fast path, can rely on already computed per-character metrics.
+ m_currentTextFragment.width = lastCharacterMetrics.width();
+ m_currentTextFragment.height = lastCharacterMetrics.height();
+ } else {
+ // Need to measure the whole range (range metrics != sum of character metrics)
+ SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(text, m_currentTextFragment.positionListOffset, m_currentTextFragment.length);
+ m_currentTextFragment.width = metrics.width();
+ m_currentTextFragment.height = metrics.height();
+ }
+
+ textBox->textFragments().append(m_currentTextFragment);
+ m_currentTextFragment = SVGTextFragment();
+}
+
+bool SVGTextLayoutEngine::parentDefinesTextLength(RenderObject* parent) const
+{
+ RenderObject* currentParent = parent;
+ while (currentParent) {
+ SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(currentParent);
+ if (textContentElement) {
+ SVGTextContentElement::SVGLengthAdjustType lengthAdjust = static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust());
+ if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACING && textContentElement->textLength().value(textContentElement) > 0)
+ return true;
+ }
+
+ if (currentParent->isSVGText())
+ return false;
+
+ currentParent = currentParent->parent();
+ }
+
+ ASSERT_NOT_REACHED();
+ return false;
+}
+
+void SVGTextLayoutEngine::beginTextPathLayout(RenderObject* object, SVGTextLayoutEngine& lineLayout)
+{
+ ASSERT(object);
+
+ m_inPathLayout = true;
+ RenderSVGTextPath* textPath = toRenderSVGTextPath(object);
+
+ m_textPath = textPath->layoutPath();
+ m_textPathStartOffset = textPath->startOffset();
+ m_textPathLength = m_textPath.length();
+ if (m_textPathStartOffset > 0 && m_textPathStartOffset <= 1)
+ m_textPathStartOffset *= m_textPathLength;
+
+ float totalLength = 0;
+ unsigned totalCharacters = 0;
+
+ lineLayout.m_chunkLayoutBuilder.buildTextChunks(lineLayout.m_lineLayoutBoxes);
+ const Vector<SVGTextChunk>& textChunks = lineLayout.m_chunkLayoutBuilder.textChunks();
+
+ unsigned size = textChunks.size();
+ for (unsigned i = 0; i < size; ++i) {
+ const SVGTextChunk& chunk = textChunks.at(i);
+
+ float length = 0;
+ unsigned characters = 0;
+ chunk.calculateLength(length, characters);
+
+ // Handle text-anchor as additional start offset for text paths.
+ m_textPathStartOffset += chunk.calculateTextAnchorShift(length);
+
+ totalLength += length;
+ totalCharacters += characters;
+ }
+
+ m_textPathCurrentOffset = m_textPathStartOffset;
+
+ // Eventually handle textLength adjustments.
+ SVGTextContentElement::SVGLengthAdjustType lengthAdjust = SVGTextContentElement::LENGTHADJUST_UNKNOWN;
+ float desiredTextLength = 0;
+
+ if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textPath)) {
+ lengthAdjust = static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust());
+ desiredTextLength = textContentElement->textLength().value(textContentElement);
+ }
+
+ if (!desiredTextLength)
+ return;
+
+ if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACING)
+ m_textPathSpacing = (desiredTextLength - totalLength) / totalCharacters;
+ else
+ m_textPathScaling = desiredTextLength / totalLength;
+}
+
+void SVGTextLayoutEngine::endTextPathLayout()
+{
+ m_inPathLayout = false;
+ m_textPath = Path();
+ m_textPathLength = 0;
+ m_textPathStartOffset = 0;
+ m_textPathCurrentOffset = 0;
+ m_textPathSpacing = 0;
+ m_textPathScaling = 1;
+}
+
+void SVGTextLayoutEngine::layoutInlineTextBox(SVGInlineTextBox* textBox)
+{
+ ASSERT(textBox);
+
+ RenderSVGInlineText* text = toRenderSVGInlineText(textBox->textRenderer());
+ ASSERT(text);
+ ASSERT(text->parent());
+ ASSERT(text->parent()->node());
+ ASSERT(text->parent()->node()->isSVGElement());
+
+ const RenderStyle* style = text->style();
+ ASSERT(style);
+
+ textBox->clearTextFragments();
+ m_isVerticalText = style->svgStyle()->isVerticalWritingMode();
+ layoutTextOnLineOrPath(textBox, text, style);
+
+ if (m_inPathLayout) {
+ m_pathLayoutBoxes.append(textBox);
+ return;
+ }
+
+ m_lineLayoutBoxes.append(textBox);
+}
+
+void SVGTextLayoutEngine::finishLayout()
+{
+ // After all text fragments are stored in their correpsonding SVGInlineTextBoxes, we can layout individual text chunks.
+ // Chunk layouting is only performed for line layout boxes, not for path layout, where it has already been done.
+ m_chunkLayoutBuilder.layoutTextChunks(m_lineLayoutBoxes);
+
+ // Finalize transform matrices, after the chunk layout corrections have been applied, and all fragment x/y positions are finalized.
+ if (!m_lineLayoutBoxes.isEmpty()) {
+#if DUMP_TEXT_FRAGMENTS > 0
+ fprintf(stderr, "Line layout: ");
+#endif
+
+ finalizeTransformMatrices(m_lineLayoutBoxes);
+ }
+
+ if (!m_pathLayoutBoxes.isEmpty()) {
+#if DUMP_TEXT_FRAGMENTS > 0
+ fprintf(stderr, "Path layout: ");
+#endif
+ finalizeTransformMatrices(m_pathLayoutBoxes);
+ }
+}
+
+void SVGTextLayoutEngine::finalizeTransformMatrices(Vector<SVGInlineTextBox*>& boxes)
+{
+ unsigned boxCount = boxes.size();
+
+#if DUMP_TEXT_FRAGMENTS > 0
+ fprintf(stderr, "Dumping all text fragments in text sub tree, %i boxes\n", boxCount);
+
+ for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
+ SVGInlineTextBox* textBox = boxes.at(boxPosition);
+ Vector<SVGTextFragment>& fragments = textBox->textFragments();
+ fprintf(stderr, "-> Box %i: Dumping text fragments for SVGInlineTextBox, textBox=%p, textRenderer=%p\n", boxPosition, textBox, textBox->textRenderer());
+ fprintf(stderr, " textBox properties, start=%i, len=%i\n", textBox->start(), textBox->len());
+ fprintf(stderr, " textRenderer properties, textLength=%i\n", textBox->textRenderer()->textLength());
+
+ const UChar* characters = textBox->textRenderer()->characters();
+
+ unsigned fragmentCount = fragments.size();
+ for (unsigned i = 0; i < fragmentCount; ++i) {
+ SVGTextFragment& fragment = fragments.at(i);
+ String fragmentString(characters + fragment.positionListOffset, fragment.length);
+ fprintf(stderr, " -> Fragment %i, x=%lf, y=%lf, width=%lf, height=%lf, positionListOffset=%i, length=%i, characters='%s'\n"
+ , i, fragment.x, fragment.y, fragment.width, fragment.height, fragment.positionListOffset, fragment.length, fragmentString.utf8().data());
+ }
+ }
+#endif
+
+
+ if (!boxCount)
+ return;
+
+ AffineTransform textBoxTransformation;
+ for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
+ SVGInlineTextBox* textBox = boxes.at(boxPosition);
+ Vector<SVGTextFragment>& fragments = textBox->textFragments();
+
+ unsigned fragmentCount = fragments.size();
+ for (unsigned i = 0; i < fragmentCount; ++i) {
+ SVGTextFragment& fragment = fragments.at(i);
+ AffineTransform& transform = fragment.transform;
+ if (!transform.isIdentity()) {
+ transform.translateRight(fragment.x, fragment.y);
+ transform.translate(-fragment.x, -fragment.y);
+ }
+
+ m_chunkLayoutBuilder.transformationForTextBox(textBox, textBoxTransformation);
+ if (textBoxTransformation.isIdentity())
+ continue;
+
+ if (transform.isIdentity())
+ transform = textBoxTransformation;
+ else
+ transform.multiply(textBoxTransformation);
+ }
+ }
+
+ boxes.clear();
+}
+
+void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, RenderSVGInlineText* text, const RenderStyle* style)
+{
+ SVGElement* lengthContext = static_cast<SVGElement*>(text->parent()->node());
+
+ RenderObject* textParent = text->parent();
+ bool definesTextLength = textParent ? parentDefinesTextLength(textParent) : false;
+
+ const SVGRenderStyle* svgStyle = style->svgStyle();
+ ASSERT(svgStyle);
+
+ SVGTextLayoutAttributes& attributes = text->layoutAttributes();
+ Vector<float>& xValues = attributes.xValues();
+ Vector<float>& yValues = attributes.yValues();
+ Vector<float>& dxValues = attributes.dxValues();
+ Vector<float>& dyValues = attributes.dyValues();
+ Vector<float>& rotateValues = attributes.rotateValues();
+ Vector<SVGTextMetrics>& textMetricsValues = attributes.textMetricsValues();
+
+ unsigned boxStart = textBox->start();
+ unsigned boxLength = textBox->len();
+ unsigned textMetricsSize = textMetricsValues.size();
+ ASSERT(textMetricsSize <= xValues.size());
+ ASSERT(textMetricsSize <= yValues.size());
+ ASSERT(xValues.size() == yValues.size());
+
+ if (boxLength > textMetricsSize)
+ textMetricsSize = boxLength;
+
+ unsigned positionListOffset = 0;
+ unsigned metricsListOffset = 0;
+ const UChar* characters = text->characters();
+
+ const Font& font = style->font();
+ SVGTextLayoutEngineSpacing spacingLayout(font);
+ SVGTextLayoutEngineBaseline baselineLayout(font);
+
+ bool didStartTextFragment = false;
+ bool applySpacingToNextCharacter = false;
+
+ float lastAngle = 0;
+ float baselineShift = baselineLayout.calculateBaselineShift(svgStyle, lengthContext);
+ baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, text);
+
+ // Main layout algorithm.
+ unsigned positionListSize = xValues.size();
+ for (; metricsListOffset < textMetricsSize && positionListOffset < positionListSize; ++metricsListOffset) {
+ SVGTextMetrics& metrics = textMetricsValues.at(metricsListOffset);
+ // Advance to text box start location.
+ if (positionListOffset < boxStart) {
+ positionListOffset += metrics.length();
+ continue;
+ }
+
+ // Stop if we've finished processing this text box.
+ if (positionListOffset >= boxStart + boxLength)
+ break;
+
+ float x = xValues.at(positionListOffset);
+ float y = yValues.at(positionListOffset);
+
+ // When we've advanced to the box start offset, determine using the original x/y values,
+ // wheter this character starts a new text chunk, before doing any further processing.
+ if (positionListOffset == boxStart)
+ textBox->setStartsNewTextChunk(text->characterStartsNewTextChunk(boxStart));
+
+ if (metrics == SVGTextMetrics::emptyMetrics()) {
+ positionListOffset += metrics.length();
+ continue;
+ }
+
+ const UChar* currentCharacter = characters + positionListOffset;
+ float angle = 0;
+ if (!rotateValues.isEmpty()) {
+ float newAngle = rotateValues.at(positionListOffset);
+ if (newAngle != SVGTextLayoutAttributes::emptyValue())
+ angle = newAngle;
+ }
+
+ // Calculate glyph orientation angle.
+ float orientationAngle = baselineLayout.calculateGlyphOrientationAngle(m_isVerticalText, svgStyle, *currentCharacter);
+
+ // Calculate glyph advance & x/y orientation shifts.
+ float xOrientationShift = 0;
+ float yOrientationShift = 0;
+ float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, metrics, orientationAngle, xOrientationShift, yOrientationShift);
+
+ // Assign current text position to x/y values, if needed.
+ updateCharacerPositionIfNeeded(x, y);
+
+ // Apply dx/dy value adjustments to current text position, if needed.
+ updateRelativePositionAdjustmentsIfNeeded(dxValues, dyValues, positionListOffset);
+
+ // Calculate SVG Fonts kerning, if needed.
+ float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, metrics.glyph());
+
+ // Calculate CSS 'kerning', 'letter-spacing' and 'word-spacing' for next character, if needed.
+ float spacing = spacingLayout.calculateCSSKerningAndSpacing(svgStyle, lengthContext, currentCharacter);
+
+ float textPathOffset = 0;
+ if (m_inPathLayout) {
+ float scaledGlyphAdvance = glyphAdvance * m_textPathScaling;
+ if (m_isVerticalText) {
+ // If there's an absolute y position available, it marks the beginning of a new position along the path.
+ if (y != SVGTextLayoutAttributes::emptyValue())
+ m_textPathCurrentOffset = y + m_textPathStartOffset;
+
+ m_textPathCurrentOffset += m_dy - kerning;
+ m_dy = 0;
+
+ // Apply dx/dy correction and setup translations that move to the glyph midpoint.
+ xOrientationShift += m_dx + baselineShift;
+ yOrientationShift -= scaledGlyphAdvance / 2;
+ } else {
+ // If there's an absolute x position available, it marks the beginning of a new position along the path.
+ if (x != SVGTextLayoutAttributes::emptyValue())
+ m_textPathCurrentOffset = x + m_textPathStartOffset;
+
+ m_textPathCurrentOffset += m_dx - kerning;
+ m_dx = 0;
+
+ // Apply dx/dy correction and setup translations that move to the glyph midpoint.
+ xOrientationShift -= scaledGlyphAdvance / 2;
+ yOrientationShift += m_dy - baselineShift;
+ }
+
+ // Calculate current offset along path.
+ textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2;
+
+ // Move to next character.
+ m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling;
+
+ // Skip character, if we're before the path.
+ if (textPathOffset < 0) {
+ positionListOffset += metrics.length();
+ continue;
+ }
+
+ // Stop processing, if the next character lies behind the path.
+ if (textPathOffset > m_textPathLength)
+ break;
+
+ bool ok = false;
+ FloatPoint point = m_textPath.pointAtLength(textPathOffset, ok);
+ ASSERT(ok);
+
+ x = point.x();
+ y = point.y();
+ angle = m_textPath.normalAngleAtLength(textPathOffset, ok);
+ ASSERT(ok);
+
+ // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle!
+ if (m_isVerticalText)
+ angle -= 90;
+ } else {
+ // Apply all previously calculated shift values.
+ if (m_isVerticalText) {
+ x += baselineShift;
+ y -= kerning;
+ } else {
+ x -= kerning;
+ y -= baselineShift;
+ }
+
+ x += m_dx;
+ y += m_dy;
+ }
+
+ // Determine wheter we have to start a new fragment.
+ bool shouldStartNewFragment = false;
+
+ if (m_dx || m_dy)
+ shouldStartNewFragment = true;
+
+ if (!shouldStartNewFragment && (m_isVerticalText || m_inPathLayout))
+ shouldStartNewFragment = true;
+
+ if (!shouldStartNewFragment && (angle || angle != lastAngle || orientationAngle))
+ shouldStartNewFragment = true;
+
+ if (!shouldStartNewFragment && (kerning || applySpacingToNextCharacter || definesTextLength))
+ shouldStartNewFragment = true;
+
+ // If we already started a fragment, close it now.
+ if (didStartTextFragment && shouldStartNewFragment) {
+ applySpacingToNextCharacter = false;
+ recordTextFragment(textBox, text, positionListOffset, textMetricsValues.at(metricsListOffset - 1));
+ }
+
+ // Eventually start a new fragment, if not yet done.
+ if (!didStartTextFragment || shouldStartNewFragment) {
+ ASSERT(!m_currentTextFragment.positionListOffset);
+ ASSERT(!m_currentTextFragment.length);
+
+ didStartTextFragment = true;
+ m_currentTextFragment.positionListOffset = positionListOffset;
+ m_currentTextFragment.x = x;
+ m_currentTextFragment.y = y;
+
+ // Build fragment transformation.
+ if (angle)
+ m_currentTextFragment.transform.rotate(angle);
+
+ if (xOrientationShift || yOrientationShift)
+ m_currentTextFragment.transform.translate(xOrientationShift, yOrientationShift);
+
+ if (orientationAngle)
+ m_currentTextFragment.transform.rotate(orientationAngle);
+
+ if (m_inPathLayout && m_textPathScaling != 1) {
+ if (m_isVerticalText)
+ m_currentTextFragment.transform.scaleNonUniform(1, m_textPathScaling);
+ else
+ m_currentTextFragment.transform.scaleNonUniform(m_textPathScaling, 1);
+ }
+ }
+
+ // Update current text position, after processing of the current character finished.
+ if (m_inPathLayout)
+ updateCurrentTextPosition(x, y, glyphAdvance);
+ else {
+ // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed.
+ if (spacing)
+ applySpacingToNextCharacter = true;
+
+ float xNew = x - m_dx;
+ float yNew = y - m_dy;
+
+ if (m_isVerticalText)
+ xNew -= baselineShift;
+ else
+ yNew += baselineShift;
+
+ updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing);
+ }
+
+ positionListOffset += metrics.length();
+ lastAngle = angle;
+ }
+
+ if (!didStartTextFragment)
+ return;
+
+ // Close last open fragment, if needed.
+ recordTextFragment(textBox, text, positionListOffset, textMetricsValues.at(metricsListOffset - 1));
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngine.h b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.h
new file mode 100644
index 0000000..ad058d8
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef SVGTextLayoutEngine_h
+#define SVGTextLayoutEngine_h
+
+#if ENABLE(SVG)
+#include "Path.h"
+#include "SVGTextChunkBuilder.h"
+#include "SVGTextFragment.h"
+#include "SVGTextMetrics.h"
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+class RenderObject;
+class RenderStyle;
+class RenderSVGInlineText;
+class SVGElement;
+class SVGInlineTextBox;
+class SVGRenderStyle;
+
+// SVGTextLayoutEngine performs the second layout phase for SVG text.
+//
+// The InlineBox tree was created, containing the text chunk information, necessary to apply
+// certain SVG specific text layout properties (text-length adjustments and text-anchor).
+// The second layout phase uses the SVGTextLayoutAttributes stored in the individual
+// RenderSVGInlineText renderers to compute the final positions for each character
+// which are stored in the SVGInlineTextBox objects.
+
+class SVGTextLayoutEngine : public Noncopyable {
+public:
+ SVGTextLayoutEngine();
+ SVGTextChunkBuilder& chunkLayoutBuilder() { return m_chunkLayoutBuilder; }
+
+ void beginTextPathLayout(RenderObject*, SVGTextLayoutEngine& lineLayout);
+ void endTextPathLayout();
+
+ void layoutInlineTextBox(SVGInlineTextBox*);
+ void finishLayout();
+
+private:
+ void updateCharacerPositionIfNeeded(float& x, float& y);
+ void updateCurrentTextPosition(float x, float y, float glyphAdvance);
+ void updateRelativePositionAdjustmentsIfNeeded(Vector<float>& dxValues, Vector<float>& dyValues, unsigned valueListPosition);
+
+ void recordTextFragment(SVGInlineTextBox*, RenderSVGInlineText*, unsigned positionListOffset, const SVGTextMetrics& lastCharacterMetrics);
+ bool parentDefinesTextLength(RenderObject*) const;
+
+ void layoutTextOnLineOrPath(SVGInlineTextBox*, RenderSVGInlineText*, const RenderStyle*);
+ void finalizeTransformMatrices(Vector<SVGInlineTextBox*>&);
+
+private:
+ Vector<SVGInlineTextBox*> m_lineLayoutBoxes;
+ Vector<SVGInlineTextBox*> m_pathLayoutBoxes;
+ SVGTextChunkBuilder m_chunkLayoutBuilder;
+
+ SVGTextFragment m_currentTextFragment;
+ float m_x;
+ float m_y;
+ float m_dx;
+ float m_dy;
+ bool m_isVerticalText;
+ bool m_inPathLayout;
+
+ // Text on path layout
+ Path m_textPath;
+ float m_textPathLength;
+ float m_textPathStartOffset;
+ float m_textPathCurrentOffset;
+ float m_textPathSpacing;
+ float m_textPathScaling;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp
new file mode 100644
index 0000000..7060ac6
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "SVGTextLayoutEngineBaseline.h"
+
+#include "Font.h"
+#include "RenderObject.h"
+#include "SVGRenderStyle.h"
+#include "SVGTextMetrics.h"
+#include "UnicodeRange.h"
+
+namespace WebCore {
+
+SVGTextLayoutEngineBaseline::SVGTextLayoutEngineBaseline(const Font& font)
+ : m_font(font)
+{
+}
+
+float SVGTextLayoutEngineBaseline::calculateBaselineShift(const SVGRenderStyle* style, SVGElement* lengthContext) const
+{
+ if (style->baselineShift() == BS_LENGTH) {
+ SVGLength baselineShiftValueLength = style->baselineShiftValue();
+ if (baselineShiftValueLength.unitType() == LengthTypePercentage)
+ return baselineShiftValueLength.valueAsPercentage() * m_font.pixelSize();
+
+ return baselineShiftValueLength.value(lengthContext);
+ }
+
+ switch (style->baselineShift()) {
+ case BS_BASELINE:
+ return 0;
+ case BS_SUB:
+ return -m_font.height() / 2;
+ case BS_SUPER:
+ return m_font.height() / 2;
+ default:
+ ASSERT_NOT_REACHED();
+ return 0;
+ }
+}
+
+EAlignmentBaseline SVGTextLayoutEngineBaseline::dominantBaselineToAlignmentBaseline(bool isVerticalText, const RenderObject* textRenderer) const
+{
+ ASSERT(textRenderer);
+ ASSERT(textRenderer->style());
+ ASSERT(textRenderer->parent());
+ ASSERT(textRenderer->parent()->style());
+
+ const SVGRenderStyle* style = textRenderer->style()->svgStyle();
+ ASSERT(style);
+
+ EDominantBaseline baseline = style->dominantBaseline();
+ if (baseline == DB_AUTO) {
+ if (isVerticalText)
+ baseline = DB_CENTRAL;
+ else
+ baseline = DB_ALPHABETIC;
+ }
+
+ switch (baseline) {
+ case DB_USE_SCRIPT:
+ // FIXME: The dominant-baseline and the baseline-table components are set by determining the predominant script of the character data content.
+ return AB_ALPHABETIC;
+ case DB_NO_CHANGE:
+ return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent());
+ case DB_RESET_SIZE:
+ return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent());
+ case DB_IDEOGRAPHIC:
+ return AB_IDEOGRAPHIC;
+ case DB_ALPHABETIC:
+ return AB_ALPHABETIC;
+ case DB_HANGING:
+ return AB_HANGING;
+ case DB_MATHEMATICAL:
+ return AB_MATHEMATICAL;
+ case DB_CENTRAL:
+ return AB_CENTRAL;
+ case DB_MIDDLE:
+ return AB_MIDDLE;
+ case DB_TEXT_AFTER_EDGE:
+ return AB_TEXT_AFTER_EDGE;
+ case DB_TEXT_BEFORE_EDGE:
+ return AB_TEXT_BEFORE_EDGE;
+ default:
+ ASSERT_NOT_REACHED();
+ return AB_AUTO;
+ }
+}
+
+float SVGTextLayoutEngineBaseline::calculateAlignmentBaselineShift(bool isVerticalText, const RenderObject* textRenderer) const
+{
+ ASSERT(textRenderer);
+ ASSERT(textRenderer->style());
+ ASSERT(textRenderer->style()->svgStyle());
+ ASSERT(textRenderer->parent());
+
+ const RenderObject* textRendererParent = textRenderer->parent();
+ ASSERT(textRendererParent);
+
+ EAlignmentBaseline baseline = textRenderer->style()->svgStyle()->alignmentBaseline();
+ if (baseline == AB_AUTO) {
+ baseline = dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent);
+ ASSERT(baseline != AB_AUTO);
+ }
+
+ // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
+ switch (baseline) {
+ case AB_BASELINE:
+ return dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent);
+ case AB_BEFORE_EDGE:
+ case AB_TEXT_BEFORE_EDGE:
+ return m_font.ascent();
+ case AB_MIDDLE:
+ return m_font.xHeight() / 2;
+ case AB_CENTRAL:
+ return (m_font.ascent() - m_font.descent()) / 2;
+ case AB_AFTER_EDGE:
+ case AB_TEXT_AFTER_EDGE:
+ case AB_IDEOGRAPHIC:
+ return m_font.descent();
+ case AB_ALPHABETIC:
+ return 0;
+ case AB_HANGING:
+ return m_font.ascent() * 8 / 10.f;
+ case AB_MATHEMATICAL:
+ return m_font.ascent() / 2;
+ default:
+ ASSERT_NOT_REACHED();
+ return 0;
+ }
+}
+
+float SVGTextLayoutEngineBaseline::calculateGlyphOrientationAngle(bool isVerticalText, const SVGRenderStyle* style, const UChar& character) const
+{
+ ASSERT(style);
+
+ switch (isVerticalText ? style->glyphOrientationVertical() : style->glyphOrientationHorizontal()) {
+ case GO_AUTO:
+ {
+ // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees.
+ // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees.
+ unsigned int unicodeRange = findCharUnicodeRange(character);
+ if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic)
+ return 90;
+
+ return 0;
+ }
+ case GO_90DEG:
+ return 90;
+ case GO_180DEG:
+ return 180;
+ case GO_270DEG:
+ return 270;
+ case GO_0DEG:
+ default:
+ return 0;
+ }
+}
+
+static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle)
+{
+ return !fabsf(fmodf(orientationAngle, 180));
+}
+
+float SVGTextLayoutEngineBaseline::calculateGlyphAdvanceAndOrientation(bool isVerticalText, SVGTextMetrics& metrics, float angle, float& xOrientationShift, float& yOrientationShift) const
+{
+ bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(angle);
+
+ // The function is based on spec requirements:
+ //
+ // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of
+ // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph.
+ //
+ // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of
+ // 180 degrees, then the current text position is incremented according to the horizontal metrics of the glyph.
+
+ // Vertical orientation handling.
+ if (isVerticalText) {
+ float ascentMinusDescent = m_font.ascent() - m_font.descent();
+ if (!angle) {
+ xOrientationShift = (ascentMinusDescent - metrics.width()) / 2;
+ yOrientationShift = m_font.ascent();
+ } else if (angle == 180)
+ xOrientationShift = (ascentMinusDescent + metrics.width()) / 2;
+ else if (angle == 270) {
+ yOrientationShift = metrics.width();
+ xOrientationShift = ascentMinusDescent;
+ }
+
+ // Vertical advance calculation.
+ if (angle && !orientationIsMultiplyOf180Degrees)
+ return metrics.width();
+
+ return metrics.height();
+ }
+
+ // Horizontal orientation handling.
+ if (angle == 90)
+ yOrientationShift = -metrics.width();
+ else if (angle == 180) {
+ xOrientationShift = metrics.width();
+ yOrientationShift = -m_font.ascent();
+ } else if (angle == 270)
+ xOrientationShift = metrics.width();
+
+ // Horizontal advance calculation.
+ if (angle && !orientationIsMultiplyOf180Degrees)
+ return metrics.height();
+
+ return metrics.width();
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h
new file mode 100644
index 0000000..d753b39
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef SVGTextLayoutEngineBaseline_h
+#define SVGTextLayoutEngineBaseline_h
+
+#if ENABLE(SVG)
+#include "SVGRenderStyleDefs.h"
+#include <wtf/Noncopyable.h>
+
+namespace WebCore {
+
+class Font;
+class RenderObject;
+class SVGElement;
+class SVGRenderStyle;
+class SVGTextMetrics;
+
+// Helper class used by SVGTextLayoutEngine to handle 'alignment-baseline' / 'dominant-baseline' and 'baseline-shift'.
+class SVGTextLayoutEngineBaseline : public Noncopyable {
+public:
+ SVGTextLayoutEngineBaseline(const Font&);
+
+ float calculateBaselineShift(const SVGRenderStyle*, SVGElement* lengthContext) const;
+ float calculateAlignmentBaselineShift(bool isVerticalText, const RenderObject* textRenderer) const;
+ float calculateGlyphOrientationAngle(bool isVerticalText, const SVGRenderStyle*, const UChar& character) const;
+ float calculateGlyphAdvanceAndOrientation(bool isVerticalText, SVGTextMetrics&, float angle, float& xOrientationShift, float& yOrientationShift) const;
+
+private:
+ EAlignmentBaseline dominantBaselineToAlignmentBaseline(bool isVerticalText, const RenderObject* textRenderer) const;
+
+ const Font& m_font;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.cpp
new file mode 100644
index 0000000..6c54b67
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "SVGTextLayoutEngineSpacing.h"
+
+#include "Font.h"
+#include "SVGRenderStyle.h"
+
+#if ENABLE(SVG_FONTS)
+#include "SVGFontElement.h"
+#endif
+
+namespace WebCore {
+
+SVGTextLayoutEngineSpacing::SVGTextLayoutEngineSpacing(const Font& font)
+ : m_font(font)
+ , m_lastCharacter(0)
+{
+}
+
+float SVGTextLayoutEngineSpacing::calculateSVGKerning(bool isVerticalText, const SVGTextMetrics::Glyph& currentGlyph)
+{
+#if ENABLE(SVG_FONTS)
+ if (!m_font.isSVGFont()) {
+ m_lastGlyph.isValid = false;
+ return 0;
+ }
+
+ SVGFontElement* svgFont = m_font.svgFont();
+ ASSERT(svgFont);
+
+ float kerning = 0;
+ if (m_lastGlyph.isValid) {
+ if (isVerticalText)
+ kerning = svgFont->verticalKerningForPairOfStringsAndGlyphs(m_lastGlyph.unicodeString, m_lastGlyph.name, currentGlyph.unicodeString, currentGlyph.name);
+ else
+ kerning = svgFont->horizontalKerningForPairOfStringsAndGlyphs(m_lastGlyph.unicodeString, m_lastGlyph.name, currentGlyph.unicodeString, currentGlyph.name);
+ }
+
+ m_lastGlyph = currentGlyph;
+ m_lastGlyph.isValid = true;
+ kerning *= m_font.size() / m_font.primaryFont()->unitsPerEm();
+ return kerning;
+#else
+ UNUSED_PARAM(isVerticalText);
+ UNUSED_PARAM(currentGlyph);
+ return false;
+#endif
+}
+
+float SVGTextLayoutEngineSpacing::calculateCSSKerningAndSpacing(const SVGRenderStyle* style, SVGElement* lengthContext, const UChar* currentCharacter)
+{
+ float kerning = 0;
+ SVGLength kerningLength = style->kerning();
+ if (kerningLength.unitType() == LengthTypePercentage)
+ kerning = kerningLength.valueAsPercentage() * m_font.pixelSize();
+ else
+ kerning = kerningLength.value(lengthContext);
+
+ const UChar* lastCharacter = m_lastCharacter;
+ m_lastCharacter = currentCharacter;
+
+ if (!kerning && !m_font.letterSpacing() && !m_font.wordSpacing())
+ return 0;
+
+ float spacing = m_font.letterSpacing() + kerning;
+ if (currentCharacter && lastCharacter && m_font.wordSpacing()) {
+ if (Font::treatAsSpace(*currentCharacter) && !Font::treatAsSpace(*lastCharacter))
+ spacing += m_font.wordSpacing();
+ }
+
+ return spacing;
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.h b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.h
new file mode 100644
index 0000000..0a6d736
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef SVGTextLayoutEngineSpacing_h
+#define SVGTextLayoutEngineSpacing_h
+
+#if ENABLE(SVG)
+#include "SVGTextMetrics.h"
+
+namespace WebCore {
+
+class Font;
+class SVGRenderStyle;
+class SVGElement;
+
+// Helper class used by SVGTextLayoutEngine to handle 'kerning' / 'letter-spacing' and 'word-spacing'.
+class SVGTextLayoutEngineSpacing : public Noncopyable {
+public:
+ SVGTextLayoutEngineSpacing(const Font&);
+
+ float calculateSVGKerning(bool isVerticalText, const SVGTextMetrics::Glyph& currentGlyph);
+ float calculateCSSKerningAndSpacing(const SVGRenderStyle*, SVGElement* lengthContext, const UChar* currentCharacter);
+
+private:
+ const Font& m_font;
+ const UChar* m_lastCharacter;
+
+#if ENABLE(SVG_FONTS)
+ SVGTextMetrics::Glyph m_lastGlyph;
+#endif
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGTextMetrics.cpp b/Source/WebCore/rendering/svg/SVGTextMetrics.cpp
new file mode 100644
index 0000000..58d0ad9
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextMetrics.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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 "SVGTextMetrics.h"
+
+#include "RenderSVGInlineText.h"
+
+namespace WebCore {
+
+SVGTextMetrics::SVGTextMetrics()
+ : m_width(0)
+ , m_height(0)
+ , m_length(0)
+{
+}
+
+SVGTextMetrics::SVGTextMetrics(const Font& font, const TextRun& run, unsigned position, unsigned textLength)
+ : m_width(0)
+ , m_height(0)
+ , m_length(0)
+{
+ int extraCharsAvailable = textLength - (position + run.length());
+ int length = 0;
+
+ m_width = font.floatWidth(run, extraCharsAvailable, length, m_glyph.name);
+ m_height = font.height();
+ m_glyph.unicodeString = String(run.characters(), length);
+ m_glyph.isValid = true;
+
+ ASSERT(length >= 0);
+ m_length = static_cast<unsigned>(length);
+}
+
+bool SVGTextMetrics::operator==(const SVGTextMetrics& other)
+{
+ return m_width == other.m_width
+ && m_height == other.m_height
+ && m_length == other.m_length
+ && m_glyph == other.m_glyph;
+}
+
+SVGTextMetrics SVGTextMetrics::emptyMetrics()
+{
+ DEFINE_STATIC_LOCAL(SVGTextMetrics, s_emptyMetrics, ());
+ s_emptyMetrics.m_length = 1;
+ return s_emptyMetrics;
+}
+
+static TextRun constructTextRun(RenderSVGInlineText* text, const UChar* characters, unsigned position, unsigned length)
+{
+ TextRun run(characters + position, length);
+
+#if ENABLE(SVG_FONTS)
+ ASSERT(text->parent());
+ run.setReferencingRenderObject(text->parent());
+#endif
+
+ // Disable any word/character rounding.
+ run.disableRoundingHacks();
+
+ // We handle letter & word spacing ourselves.
+ run.disableSpacing();
+ return run;
+}
+
+SVGTextMetrics SVGTextMetrics::measureCharacterRange(RenderSVGInlineText* text, unsigned position, unsigned length)
+{
+ ASSERT(text);
+ ASSERT(text->style());
+
+ TextRun run(constructTextRun(text, text->characters(), position, length));
+ return SVGTextMetrics(text->style()->font(), run, position, text->textLength());
+}
+
+void SVGTextMetrics::measureAllCharactersIndividually(RenderSVGInlineText* text, Vector<SVGTextMetrics>& allMetrics)
+{
+ ASSERT(text);
+ ASSERT(text->style());
+
+ const Font& font = text->style()->font();
+ const UChar* characters = text->characters();
+ unsigned length = text->textLength();
+
+ TextRun run(constructTextRun(text, 0, 0, 0));
+ for (unsigned position = 0; position < length; ) {
+ run.setText(characters + position, 1);
+
+ SVGTextMetrics metrics(font, run, position, text->textLength());
+ allMetrics.append(metrics);
+ position += metrics.length();
+ }
+}
+
+}
+
+#endif // ENABLE(SVG)
diff --git a/Source/WebCore/rendering/svg/SVGTextMetrics.h b/Source/WebCore/rendering/svg/SVGTextMetrics.h
new file mode 100644
index 0000000..ba18589
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextMetrics.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef SVGTextMetrics_h
+#define SVGTextMetrics_h
+
+#if ENABLE(SVG)
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class Font;
+class RenderSVGInlineText;
+class TextRun;
+
+class SVGTextMetrics {
+public:
+ static SVGTextMetrics emptyMetrics();
+ static SVGTextMetrics measureCharacterRange(RenderSVGInlineText*, unsigned position, unsigned length);
+ static void measureAllCharactersIndividually(RenderSVGInlineText*, Vector<SVGTextMetrics>&);
+
+ bool operator==(const SVGTextMetrics&);
+
+ float width() const { return m_width; }
+ float height() const { return m_height; }
+ unsigned length() const { return m_length; }
+
+ struct Glyph {
+ Glyph()
+ : isValid(false)
+ {
+ }
+
+ bool operator==(const Glyph& other)
+ {
+ return isValid == other.isValid
+ && name == other.name
+ && unicodeString == other.unicodeString;
+ }
+
+ bool isValid;
+ String name;
+ String unicodeString;
+ };
+
+ // Only useful when measuring individual characters, to lookup ligatures.
+ const Glyph& glyph() const { return m_glyph; }
+
+private:
+ SVGTextMetrics();
+ SVGTextMetrics(const Font&, const TextRun&, unsigned position, unsigned textLength);
+
+ float m_width;
+ float m_height;
+ unsigned m_length;
+ Glyph m_glyph;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SVG)
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGTextQuery.cpp b/Source/WebCore/rendering/svg/SVGTextQuery.cpp
new file mode 100644
index 0000000..fcc7924
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextQuery.cpp
@@ -0,0 +1,562 @@
+/*
+ Copyright (C) Research In Motion Limited 2010. All rights reserved.
+
+ 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"
+#include "SVGTextQuery.h"
+
+#if ENABLE(SVG)
+#include "FloatConversion.h"
+#include "InlineFlowBox.h"
+#include "RenderBlock.h"
+#include "RenderInline.h"
+#include "RenderSVGInlineText.h"
+#include "SVGInlineTextBox.h"
+#include "SVGTextMetrics.h"
+#include "VisiblePosition.h"
+
+#include <wtf/MathExtras.h>
+
+namespace WebCore {
+
+// Base structure for callback user data
+struct SVGTextQuery::Data {
+ Data()
+ : isVerticalText(false)
+ , processedCharacters(0)
+ , textRenderer(0)
+ , textBox(0)
+ {
+ }
+
+ bool isVerticalText;
+ unsigned processedCharacters;
+ RenderSVGInlineText* textRenderer;
+ const SVGInlineTextBox* textBox;
+};
+
+static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer)
+{
+ if (!renderer)
+ return 0;
+
+ if (renderer->isRenderBlock()) {
+ // If we're given a block element, it has to be a RenderSVGText.
+ ASSERT(renderer->isSVGText());
+ RenderBlock* renderBlock = toRenderBlock(renderer);
+
+ // RenderSVGText only ever contains a single line box.
+ InlineFlowBox* flowBox = renderBlock->firstLineBox();
+ ASSERT(flowBox == renderBlock->lastLineBox());
+ return flowBox;
+ }
+
+ if (renderer->isRenderInline()) {
+ // We're given a RenderSVGInline or objects that derive from it (RenderSVGTSpan / RenderSVGTextPath)
+ RenderInline* renderInline = toRenderInline(renderer);
+
+ // RenderSVGInline only ever contains a single line box.
+ InlineFlowBox* flowBox = renderInline->firstLineBox();
+ ASSERT(flowBox == renderInline->lastLineBox());
+ return flowBox;
+ }
+
+ ASSERT_NOT_REACHED();
+ return 0;
+}
+
+static inline float mapLengthThroughFragmentTransformation(const SVGTextFragment& fragment, bool isVerticalText, float length)
+{
+ if (fragment.transform.isIdentity())
+ return length;
+
+ if (isVerticalText)
+ return narrowPrecisionToFloat(static_cast<double>(length) * fragment.transform.yScale());
+
+ return narrowPrecisionToFloat(static_cast<double>(length) * fragment.transform.xScale());
+}
+
+SVGTextQuery::SVGTextQuery(RenderObject* renderer)
+{
+ collectTextBoxesInFlowBox(flowBoxForRenderer(renderer));
+}
+
+void SVGTextQuery::collectTextBoxesInFlowBox(InlineFlowBox* flowBox)
+{
+ if (!flowBox)
+ return;
+
+ for (InlineBox* child = flowBox->firstChild(); child; child = child->nextOnLine()) {
+ if (child->isInlineFlowBox()) {
+ // Skip generated content.
+ if (!child->renderer()->node())
+ continue;
+
+ collectTextBoxesInFlowBox(static_cast<InlineFlowBox*>(child));
+ continue;
+ }
+
+ ASSERT(child->isSVGInlineTextBox());
+ m_textBoxes.append(static_cast<SVGInlineTextBox*>(child));
+ }
+}
+
+bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextFragmentCallback fragmentCallback) const
+{
+ ASSERT(!m_textBoxes.isEmpty());
+
+ unsigned processedCharacters = 0;
+ unsigned textBoxCount = m_textBoxes.size();
+
+ // Loop over all text boxes
+ for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) {
+ queryData->textBox = m_textBoxes.at(textBoxPosition);
+ queryData->textRenderer = toRenderSVGInlineText(queryData->textBox->textRenderer());
+ ASSERT(queryData->textRenderer);
+ ASSERT(queryData->textRenderer->style());
+ ASSERT(queryData->textRenderer->style()->svgStyle());
+
+ queryData->isVerticalText = queryData->textRenderer->style()->svgStyle()->isVerticalWritingMode();
+ const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments();
+
+ // Loop over all text fragments in this text box, firing a callback for each.
+ unsigned fragmentCount = fragments.size();
+ for (unsigned i = 0; i < fragmentCount; ++i) {
+ const SVGTextFragment& fragment = fragments.at(i);
+ if ((this->*fragmentCallback)(queryData, fragment))
+ return true;
+
+ processedCharacters += fragment.length;
+ }
+
+ queryData->processedCharacters = processedCharacters;
+ }
+
+ return false;
+}
+
+bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const
+{
+ // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text fragment.
+ startPosition -= queryData->processedCharacters;
+ endPosition -= queryData->processedCharacters;
+
+ if (startPosition >= endPosition || startPosition < 0 || endPosition < 0)
+ return false;
+
+ modifyStartEndPositionsRespectingLigatures(queryData, startPosition, endPosition);
+ if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition))
+ return false;
+
+ ASSERT(startPosition < endPosition);
+ return true;
+}
+
+void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, int& startPosition, int& endPosition) const
+{
+ const SVGTextLayoutAttributes& layoutAttributes = queryData->textRenderer->layoutAttributes();
+ const Vector<float>& xValues = layoutAttributes.xValues();
+ const Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes.textMetricsValues();
+
+ unsigned boxStart = queryData->textBox->start();
+ unsigned boxLength = queryData->textBox->len();
+
+ unsigned textMetricsOffset = 0;
+ unsigned textMetricsSize = textMetricsValues.size();
+
+ unsigned positionOffset = 0;
+ unsigned positionSize = xValues.size();
+
+ bool alterStartPosition = true;
+ bool alterEndPosition = true;
+
+ int lastPositionOffset = -1;
+ for (; textMetricsOffset < textMetricsSize && positionOffset < positionSize; ++textMetricsOffset) {
+ const SVGTextMetrics& metrics = textMetricsValues.at(textMetricsOffset);
+
+ // Advance to text box start location.
+ if (positionOffset < boxStart) {
+ positionOffset += metrics.length();
+ continue;
+ }
+
+ // Stop if we've finished processing this text box.
+ if (positionOffset >= boxStart + boxLength)
+ break;
+
+ // If the start position maps to a character in the metrics list, we don't need to modify it.
+ if (startPosition == static_cast<int>(positionOffset))
+ alterStartPosition = false;
+
+ // If the start position maps to a character in the metrics list, we don't need to modify it.
+ if (endPosition == static_cast<int>(positionOffset))
+ alterEndPosition = false;
+
+ // Detect ligatures.
+ if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
+ if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) {
+ startPosition = lastPositionOffset;
+ alterStartPosition = false;
+ }
+
+ if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) {
+ endPosition = positionOffset;
+ alterEndPosition = false;
+ }
+ }
+
+ if (!alterStartPosition && !alterEndPosition)
+ break;
+
+ lastPositionOffset = positionOffset;
+ positionOffset += metrics.length();
+ }
+
+ if (!alterStartPosition && !alterEndPosition)
+ return;
+
+ if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
+ if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) {
+ startPosition = lastPositionOffset;
+ alterStartPosition = false;
+ }
+
+ if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) {
+ endPosition = positionOffset;
+ alterEndPosition = false;
+ }
+ }
+}
+
+// numberOfCharacters() implementation
+bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const
+{
+ // no-op
+ return false;
+}
+
+unsigned SVGTextQuery::numberOfCharacters() const
+{
+ if (m_textBoxes.isEmpty())
+ return 0;
+
+ Data data;
+ executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback);
+ return data.processedCharacters;
+}
+
+// textLength() implementation
+struct TextLengthData : SVGTextQuery::Data {
+ TextLengthData()
+ : textLength(0)
+ {
+ }
+
+ float textLength;
+};
+
+bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
+{
+ TextLengthData* data = static_cast<TextLengthData*>(queryData);
+
+ float fragmentLength = queryData->isVerticalText ? fragment.height : fragment.width;
+ data->textLength += mapLengthThroughFragmentTransformation(fragment, queryData->isVerticalText, fragmentLength);
+ return false;
+}
+
+float SVGTextQuery::textLength() const
+{
+ if (m_textBoxes.isEmpty())
+ return 0;
+
+ TextLengthData data;
+ executeQuery(&data, &SVGTextQuery::textLengthCallback);
+ return data.textLength;
+}
+
+// subStringLength() implementation
+struct SubStringLengthData : SVGTextQuery::Data {
+ SubStringLengthData(unsigned queryStartPosition, unsigned queryLength)
+ : startPosition(queryStartPosition)
+ , length(queryLength)
+ , subStringLength(0)
+ {
+ }
+
+ unsigned startPosition;
+ unsigned length;
+
+ float subStringLength;
+};
+
+bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const
+{
+ SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData);
+
+ int startPosition = data->startPosition;
+ int endPosition = startPosition + data->length;
+ if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
+ return false;
+
+ SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset + startPosition, endPosition - startPosition);
+ float fragmentLength = queryData->isVerticalText ? metrics.height() : metrics.width();
+
+ data->subStringLength += mapLengthThroughFragmentTransformation(fragment, queryData->isVerticalText, fragmentLength);
+ return false;
+}
+
+float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const
+{
+ if (m_textBoxes.isEmpty())
+ return 0;
+
+ SubStringLengthData data(startPosition, length);
+ executeQuery(&data, &SVGTextQuery::subStringLengthCallback);
+ return data.subStringLength;
+}
+
+// startPositionOfCharacter() implementation
+struct StartPositionOfCharacterData : SVGTextQuery::Data {
+ StartPositionOfCharacterData(unsigned queryPosition)
+ : position(queryPosition)
+ {
+ }
+
+ unsigned position;
+ FloatPoint startPosition;
+};
+
+bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
+{
+ StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData);
+
+ int startPosition = data->position;
+ int endPosition = startPosition + 1;
+ if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
+ return false;
+
+ data->startPosition = FloatPoint(fragment.x, fragment.y);
+
+ if (startPosition) {
+ SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset, startPosition);
+ if (queryData->isVerticalText)
+ data->startPosition.move(0, metrics.height());
+ else
+ data->startPosition.move(metrics.width(), 0);
+ }
+
+ if (fragment.transform.isIdentity())
+ return true;
+
+ data->startPosition = fragment.transform.mapPoint(data->startPosition);
+ return true;
+}
+
+FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const
+{
+ if (m_textBoxes.isEmpty())
+ return FloatPoint();
+
+ StartPositionOfCharacterData data(position);
+ executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback);
+ return data.startPosition;
+}
+
+// endPositionOfCharacter() implementation
+struct EndPositionOfCharacterData : SVGTextQuery::Data {
+ EndPositionOfCharacterData(unsigned queryPosition)
+ : position(queryPosition)
+ {
+ }
+
+ unsigned position;
+ FloatPoint endPosition;
+};
+
+bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
+{
+ EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData);
+
+ int startPosition = data->position;
+ int endPosition = startPosition + 1;
+ if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
+ return false;
+
+ data->endPosition = FloatPoint(fragment.x, fragment.y);
+
+ SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset, startPosition + 1);
+ if (queryData->isVerticalText)
+ data->endPosition.move(0, metrics.height());
+ else
+ data->endPosition.move(metrics.width(), 0);
+
+ if (fragment.transform.isIdentity())
+ return true;
+
+ data->endPosition = fragment.transform.mapPoint(data->endPosition);
+ return true;
+}
+
+FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const
+{
+ if (m_textBoxes.isEmpty())
+ return FloatPoint();
+
+ EndPositionOfCharacterData data(position);
+ executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback);
+ return data.endPosition;
+}
+
+// rotationOfCharacter() implementation
+struct RotationOfCharacterData : SVGTextQuery::Data {
+ RotationOfCharacterData(unsigned queryPosition)
+ : position(queryPosition)
+ , rotation(0)
+ {
+ }
+
+ unsigned position;
+ float rotation;
+};
+
+bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
+{
+ RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData);
+
+ int startPosition = data->position;
+ int endPosition = startPosition + 1;
+ if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
+ return false;
+
+ AffineTransform newTransform(fragment.transform);
+ newTransform.scale(1 / fragment.transform.xScale(), 1 / fragment.transform.yScale());
+ data->rotation = narrowPrecisionToFloat(rad2deg(atan2(newTransform.b(), newTransform.a())));
+ return true;
+}
+
+float SVGTextQuery::rotationOfCharacter(unsigned position) const
+{
+ if (m_textBoxes.isEmpty())
+ return 0;
+
+ RotationOfCharacterData data(position);
+ executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback);
+ return data.rotation;
+}
+
+// extentOfCharacter() implementation
+struct ExtentOfCharacterData : SVGTextQuery::Data {
+ ExtentOfCharacterData(unsigned queryPosition)
+ : position(queryPosition)
+ {
+ }
+
+ unsigned position;
+ FloatRect extent;
+};
+
+static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent)
+{
+ extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->style()->font().ascent()));
+
+ if (startPosition) {
+ SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset, startPosition);
+ if (queryData->isVerticalText)
+ extent.move(0, metrics.height());
+ else
+ extent.move(metrics.width(), 0);
+ }
+
+ SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset + startPosition, 1);
+ extent.setSize(FloatSize(metrics.width(), metrics.height()));
+
+ if (fragment.transform.isIdentity())
+ return;
+
+ extent = fragment.transform.mapRect(extent);
+}
+
+bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const
+{
+ ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData);
+
+ int startPosition = data->position;
+ int endPosition = startPosition + 1;
+ if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
+ return false;
+
+ calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent);
+ return true;
+}
+
+FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const
+{
+ if (m_textBoxes.isEmpty())
+ return FloatRect();
+
+ ExtentOfCharacterData data(position);
+ executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback);
+ return data.extent;
+}
+
+// characterNumberAtPosition() implementation
+struct CharacterNumberAtPositionData : SVGTextQuery::Data {
+ CharacterNumberAtPositionData(const FloatPoint& queryPosition)
+ : position(queryPosition)
+ {
+ }
+
+ FloatPoint position;
+};
+
+bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const
+{
+ CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData);
+
+ FloatRect extent;
+ for (unsigned i = 0; i < fragment.length; ++i) {
+ int startPosition = data->processedCharacters + i;
+ int endPosition = startPosition + 1;
+ if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition))
+ continue;
+
+ calculateGlyphBoundaries(queryData, fragment, startPosition, extent);
+ if (extent.contains(data->position)) {
+ data->processedCharacters += i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const
+{
+ if (m_textBoxes.isEmpty())
+ return -1;
+
+ CharacterNumberAtPositionData data(position);
+ if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback))
+ return -1;
+
+ return data.processedCharacters;
+}
+
+}
+
+#endif
diff --git a/Source/WebCore/rendering/svg/SVGTextQuery.h b/Source/WebCore/rendering/svg/SVGTextQuery.h
new file mode 100644
index 0000000..9a671f4
--- /dev/null
+++ b/Source/WebCore/rendering/svg/SVGTextQuery.h
@@ -0,0 +1,75 @@
+/*
+ Copyright (C) Research In Motion Limited 2010. All rights reserved.
+
+ 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.
+*/
+
+#ifndef SVGTextQuery_h
+#define SVGTextQuery_h
+
+#if ENABLE(SVG)
+#include "FloatRect.h"
+#include "SVGTextFragment.h"
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+class InlineFlowBox;
+class RenderObject;
+class SVGInlineTextBox;
+
+class SVGTextQuery {
+public:
+ SVGTextQuery(RenderObject*);
+
+ unsigned numberOfCharacters() const;
+ float textLength() const;
+ float subStringLength(unsigned startPosition, unsigned length) const;
+ FloatPoint startPositionOfCharacter(unsigned position) const;
+ FloatPoint endPositionOfCharacter(unsigned position) const;
+ float rotationOfCharacter(unsigned position) const;
+ FloatRect extentOfCharacter(unsigned position) const;
+ int characterNumberAtPosition(const FloatPoint&) const;
+
+ // Public helper struct. Private classes in SVGTextQuery inherit from it.
+ struct Data;
+
+private:
+ typedef bool (SVGTextQuery::*ProcessTextFragmentCallback)(Data*, const SVGTextFragment&) const;
+ bool executeQuery(Data*, ProcessTextFragmentCallback) const;
+
+ void collectTextBoxesInFlowBox(InlineFlowBox*);
+ bool mapStartEndPositionsIntoFragmentCoordinates(Data*, const SVGTextFragment&, int& startPosition, int& endPosition) const;
+ void modifyStartEndPositionsRespectingLigatures(Data*, int& startPosition, int& endPosition) const;
+
+private:
+ bool numberOfCharactersCallback(Data*, const SVGTextFragment&) const;
+ bool textLengthCallback(Data*, const SVGTextFragment&) const;
+ bool subStringLengthCallback(Data*, const SVGTextFragment&) const;
+ bool startPositionOfCharacterCallback(Data*, const SVGTextFragment&) const;
+ bool endPositionOfCharacterCallback(Data*, const SVGTextFragment&) const;
+ bool rotationOfCharacterCallback(Data*, const SVGTextFragment&) const;
+ bool extentOfCharacterCallback(Data*, const SVGTextFragment&) const;
+ bool characterNumberAtPositionCallback(Data*, const SVGTextFragment&) const;
+
+private:
+ Vector<SVGInlineTextBox*> m_textBoxes;
+};
+
+}
+
+#endif
+#endif