WebCore/rendering/SVGInlineTextBox.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/rendering/SVGInlineTextBox.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,875 @@
+/**
+ * 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 "RenderSVGResource.h"
+#include "SVGRootInlineBox.h"
+#include "SVGTextLayoutUtilities.h"
+
+#include <float.h>
+
+using namespace std;
+
+namespace WebCore {
+
+SVGInlineTextBox::SVGInlineTextBox(RenderObject* object)
+    : InlineTextBox(object)
+    , m_height(0)
+    , m_paintingResource(0)
+    , m_paintingResourceMode(ApplyToDefaultMode)
+{
+}
+
+SVGRootInlineBox* SVGInlineTextBox::svgRootInlineBox() const
+{
+    // Find associated root inline box
+    InlineFlowBox* parentBox = parent();
+
+    while (parentBox && !parentBox->isRootInlineBox())
+        parentBox = parentBox->parent();
+
+    ASSERT(parentBox);
+    ASSERT(parentBox->isRootInlineBox());
+
+    if (!parentBox->isSVGRootInlineBox())
+        return 0;
+
+    return static_cast<SVGRootInlineBox*>(parentBox);
+}
+
+void SVGInlineTextBox::measureCharacter(RenderStyle* style, int position, int& charsConsumed, String& glyphName, String& unicodeString, float& glyphWidth, float& glyphHeight) const
+{
+    ASSERT(style);
+
+    int offset = direction() == RTL ? end() - position : start() + position;
+    int extraCharsAvailable = len() - position - 1;
+    const UChar* characters = textRenderer()->characters();
+
+    const Font& font = style->font();
+    glyphWidth = font.floatWidth(svgTextRunForInlineTextBox(characters + offset, 1, style, this), extraCharsAvailable, charsConsumed, glyphName);
+    glyphHeight = font.height();
+
+    // The unicodeString / glyphName pair is needed for kerning calculations.
+    unicodeString = String(characters + offset, charsConsumed);
+}
+
+int SVGInlineTextBox::offsetForPosition(int xCoordinate, bool includePartialGlyphs) const
+{
+    ASSERT(!m_currentChunkPart.isValid());
+    float x = xCoordinate;
+
+    RenderText* textRenderer = this->textRenderer();
+    ASSERT(textRenderer);
+
+    RenderStyle* style = textRenderer->style();
+    ASSERT(style);
+
+    RenderBlock* containingBlock = textRenderer->containingBlock();
+    ASSERT(containingBlock);
+
+    // Move incoming relative x position to absolute position, as the character origins stored in the chunk parts use absolute coordinates
+    x += containingBlock->x();
+
+    // Figure out which text chunk part is hit
+    SVGTextChunkPart hitPart;
+
+    const Vector<SVGTextChunkPart>::const_iterator end = m_svgTextChunkParts.end();
+    for (Vector<SVGTextChunkPart>::const_iterator it = m_svgTextChunkParts.begin(); it != end; ++it) {
+        const SVGTextChunkPart& part = *it;
+
+        // Check whether we're past the hit part.
+        if (x < part.firstCharacter->x)
+            break;
+
+        hitPart = part;
+    }
+
+    // If we did not hit anything, just exit.
+    if (!hitPart.isValid())
+        return 0;
+
+    // Before calling Font::offsetForPosition(), subtract the start position of the first character
+    // in the hit text chunk part, to pass in coordinates, which are relative to the text chunk part, as
+    // constructTextRun() only builds a TextRun for the current chunk part, not the whole inline text box.
+    x -= hitPart.firstCharacter->x;
+
+    m_currentChunkPart = hitPart;
+    TextRun textRun(constructTextRun(style));
+    m_currentChunkPart = SVGTextChunkPart();
+
+    // Eventually handle lengthAdjust="spacingAndGlyphs".
+    // FIXME: Need to revisit the whole offsetForPosition concept for vertical text selection.
+    if (!m_chunkTransformation.isIdentity())
+        textRun.setHorizontalGlyphStretch(narrowPrecisionToFloat(m_chunkTransformation.a()));
+
+    return hitPart.offset + style->font().offsetForPosition(textRun, x, includePartialGlyphs);
+}
+
+int SVGInlineTextBox::positionForOffset(int) const
+{
+    // SVG doesn't use the offset <-> position selection system. 
+    ASSERT_NOT_REACHED();
+    return 0;
+}
+
+FloatRect SVGInlineTextBox::selectionRectForTextChunkPart(const SVGTextChunkPart& part, int partStartPos, int partEndPos, RenderStyle* style)
+{
+    // Map startPos/endPos positions into chunk part
+    mapStartEndPositionsIntoChunkPartCoordinates(partStartPos, partEndPos, part);
+
+    if (partStartPos >= partEndPos)
+        return FloatRect();
+
+    // Set current chunk part, so constructTextRun() works properly.
+    m_currentChunkPart = part;
+
+    const Font& font = style->font();
+    Vector<SVGChar>::const_iterator character = part.firstCharacter;
+    FloatPoint textOrigin(character->x, character->y - font.ascent());
+
+    FloatRect partRect(font.selectionRectForText(constructTextRun(style), textOrigin, part.height, partStartPos, partEndPos));
+    m_currentChunkPart = SVGTextChunkPart();
+
+    return character->characterTransform().mapRect(partRect);
+}
+
+IntRect SVGInlineTextBox::selectionRect(int, int, int startPos, int endPos)
+{
+    ASSERT(!m_currentChunkPart.isValid());
+
+    int boxStart = start();
+    startPos = max(startPos - boxStart, 0);
+    endPos = min(endPos - boxStart, static_cast<int>(len()));
+
+    if (startPos >= endPos)
+        return IntRect();
+
+    RenderText* text = textRenderer();
+    ASSERT(text);
+
+    RenderStyle* style = text->style();
+    ASSERT(style);
+
+    FloatRect selectionRect;
+
+    // Figure out which text chunk part is hit
+    const Vector<SVGTextChunkPart>::const_iterator end = m_svgTextChunkParts.end();
+    for (Vector<SVGTextChunkPart>::const_iterator it = m_svgTextChunkParts.begin(); it != end; ++it)
+        selectionRect.unite(selectionRectForTextChunkPart(*it, startPos, endPos, style));
+
+    // Resepect possible chunk transformation
+    if (m_chunkTransformation.isIdentity())
+        return enclosingIntRect(selectionRect);
+
+    return enclosingIntRect(m_chunkTransformation.mapRect(selectionRect));
+}
+
+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);
+
+    RenderStyle* style = parentRenderer->style();
+    ASSERT(style);
+
+    const SVGRenderStyle* svgStyle = style->svgStyle();
+    ASSERT(svgStyle);
+
+    bool hasFill = svgStyle->hasFill();
+    bool hasStroke = svgStyle->hasStroke();
+    bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection;
+
+    // Determine whether or not we're selected.
+    bool isPrinting = parentRenderer->document()->printing();
+    bool hasSelection = !isPrinting && selectionState() != RenderObject::SelectionNone;
+    if (!hasSelection && paintSelectedTextOnly)
+        return;
+
+    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;
+    }
+
+    // Compute text match marker rects. It needs to traverse all text chunk parts to figure
+    // out the union selection rect of all text chunk parts that contribute to the selection.
+    computeTextMatchMarkerRect(style);
+    ASSERT(!m_currentChunkPart.isValid());
+
+    const Vector<SVGTextChunkPart>::const_iterator end = m_svgTextChunkParts.end();
+    for (Vector<SVGTextChunkPart>::const_iterator it = m_svgTextChunkParts.begin(); it != end; ++it) {
+        ASSERT(!m_paintingResource);
+
+        // constructTextRun() uses the current chunk part to figure out what text to render.
+        m_currentChunkPart = *it;
+        paintInfo.context->save();
+
+        // Prepare context and draw text
+        if (!m_chunkTransformation.isIdentity())
+            paintInfo.context->concatCTM(m_chunkTransformation);
+
+        Vector<SVGChar>::const_iterator firstCharacter = m_currentChunkPart.firstCharacter;
+        AffineTransform characterTransform = firstCharacter->characterTransform();
+        if (!characterTransform.isIdentity())
+            paintInfo.context->concatCTM(characterTransform);
+
+        FloatPoint textOrigin(firstCharacter->x, firstCharacter->y);
+
+        // Draw background once (not in both fill/stroke phases)
+        if (!isPrinting && !paintSelectedTextOnly && hasSelection)
+            paintSelection(paintInfo.context, textOrigin, style);
+
+        // 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, textOrigin, UNDERLINE, hasSelection);
+        if (decorations & OVERLINE)
+            paintDecoration(paintInfo.context, textOrigin, OVERLINE, hasSelection);
+
+        // Fill text
+        if (hasFill) {
+            m_paintingResourceMode = ApplyToFillMode | ApplyToTextMode;
+            paintText(paintInfo.context, textOrigin, style, selectionStyle, hasSelection, paintSelectedTextOnly);
+        }
+
+        // Stroke text
+        if (hasStroke) {
+            m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode;
+            paintText(paintInfo.context, textOrigin, style, selectionStyle, 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, textOrigin, LINE_THROUGH, hasSelection);
+
+        m_paintingResourceMode = ApplyToDefaultMode;
+        paintInfo.context->restore();
+    }
+
+    m_currentChunkPart = SVGTextChunkPart();
+    ASSERT(!m_paintingResource);
+}
+
+bool SVGInlineTextBox::acquirePaintingResource(GraphicsContext*& context, RenderStyle* style)
+{
+    ASSERT(m_paintingResourceMode != ApplyToDefaultMode);
+
+    RenderObject* parentRenderer = parent()->renderer();
+    ASSERT(parentRenderer);
+
+    if (m_paintingResourceMode & ApplyToFillMode)
+        m_paintingResource = RenderSVGResource::fillPaintingResource(parentRenderer, style);
+    else if (m_paintingResourceMode & ApplyToStrokeMode)
+        m_paintingResource = RenderSVGResource::strokePaintingResource(parentRenderer, style);
+    else {
+        // We're either called for stroking or filling.
+        ASSERT_NOT_REACHED();
+    }
+
+    if (!m_paintingResource)
+        return false;
+
+    m_paintingResource->applyResource(parentRenderer, style, context, m_paintingResourceMode);
+    return true;
+}
+
+void SVGInlineTextBox::releasePaintingResource(GraphicsContext*& context)
+{
+    ASSERT(m_paintingResource);
+
+    RenderObject* parentRenderer = parent()->renderer();
+    ASSERT(parentRenderer);
+
+    m_paintingResource->postApplyResource(parentRenderer, context, m_paintingResourceMode);
+    m_paintingResource = 0;
+}
+
+bool SVGInlineTextBox::prepareGraphicsContextForTextPainting(GraphicsContext*& context, TextRun& textRun, RenderStyle* style)
+{
+    bool acquiredResource = acquirePaintingResource(context, 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);
+
+#if ENABLE(SVG_FONTS)
+    textRun.setActivePaintingResource(0);
+#endif
+}
+
+TextRun SVGInlineTextBox::constructTextRun(RenderStyle* style) const
+{
+    ASSERT(m_currentChunkPart.isValid());
+    return svgTextRunForInlineTextBox(textRenderer()->text()->characters() + start() + m_currentChunkPart.offset, m_currentChunkPart.length, style, this);
+}
+
+void SVGInlineTextBox::mapStartEndPositionsIntoChunkPartCoordinates(int& startPos, int& endPos, const SVGTextChunkPart& part) const
+{
+    if (startPos >= endPos)
+        return;
+
+    // Take <text x="10 50 100">ABC</text> as example. We're called three times from paint(), because all absolute positioned
+    // characters are drawn on their own. For each of them we want to find out whehter it's selected. startPos=0, endPos=1
+    // could mean A, B or C is hit, depending on which chunk part is processed at the moment. With the offset & length values
+    // of each chunk part we can easily find out which one is meant to be selected. Bail out for the other chunk parts.
+    // If starPos is behind the current chunk or the endPos ends before this text chunk part, we're not meant to be selected.
+    if (startPos >= part.offset + part.length || endPos <= part.offset) {
+        startPos = 0;
+        endPos = -1;
+        return;
+    }
+
+    // The current processed chunk part is hit. When painting the selection, constructTextRun() builds
+    // a TextRun object whose startPos is 0 and endPos is chunk part length. The code below maps the incoming
+    // startPos/endPos range into a [0, part length] coordinate system, relative to the current chunk part.
+    if (startPos < part.offset)
+        startPos = 0;
+    else
+        startPos -= part.offset;
+
+    if (endPos > part.offset + part.length)
+        endPos = part.length;
+    else {
+        ASSERT(endPos >= part.offset);
+        endPos -= part.offset;
+    }
+
+    ASSERT(startPos < endPos);
+}
+
+void SVGInlineTextBox::selectionStartEnd(int& startPos, int& endPos)
+{
+    InlineTextBox::selectionStartEnd(startPos, endPos);
+
+    if (!m_currentChunkPart.isValid())
+        return;
+
+    mapStartEndPositionsIntoChunkPartCoordinates(startPos, endPos, m_currentChunkPart);
+}
+
+void SVGInlineTextBox::computeTextMatchMarkerRect(RenderStyle* style)
+{
+    ASSERT(!m_currentChunkPart.isValid());
+    Node* node = renderer()->node();
+    if (!node || !node->inDocument())
+        return;
+
+    Document* document = renderer()->document();
+    Vector<DocumentMarker> markers = document->markersForNode(renderer()->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;
+        int partStartPos = max(marker.startOffset - start(), static_cast<unsigned>(0));
+        int partEndPos = min(marker.endOffset - start(), static_cast<unsigned>(len()));
+
+        // Iterate over all text chunk parts, to see which ones have to be highlighted
+        const Vector<SVGTextChunkPart>::const_iterator end = m_svgTextChunkParts.end();
+        for (Vector<SVGTextChunkPart>::const_iterator it = m_svgTextChunkParts.begin(); it != end; ++it)
+            markerRect.unite(selectionRectForTextChunkPart(*it, partStartPos, partEndPos, style));
+
+        if (!m_chunkTransformation.isIdentity())
+            markerRect = m_chunkTransformation.mapRect(markerRect);
+
+        document->setRenderedRectForMarker(node, marker, renderer()->localToAbsoluteQuad(markerRect).enclosingBoundingBox());
+    }
+}
+
+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, ETextDecoration decoration)
+{
+    // Lookup render object which has text-decoration set.
+    RenderObject* renderer = 0;
+    while (parentBox) {
+        renderer = parentBox->renderer();
+
+        // Explicitely check textDecoration() not textDecorationsInEffect(), which is inherited to
+        // children, as we want to lookup the render object whose style defined the text-decoration.
+        if (renderer->style() && renderer->style()->textDecoration() & decoration)
+            break;
+
+        parentBox = parentBox->parent();
+    }
+
+    ASSERT(renderer);
+    return renderer;
+}
+
+void SVGInlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& textOrigin, ETextDecoration decoration, bool hasSelection)
+{
+    // 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(), decoration);
+    RenderStyle* decorationStyle = decorationRenderer->style();
+    ASSERT(decorationStyle);
+
+    const SVGRenderStyle* svgDecorationStyle = decorationStyle->svgStyle();
+    ASSERT(svgDecorationStyle);
+
+    bool hasDecorationFill = svgDecorationStyle->hasFill();
+    bool hasDecorationStroke = svgDecorationStyle->hasStroke();
+
+    if (hasSelection) {
+        if (RenderStyle* pseudoStyle = decorationRenderer->getCachedPseudoStyle(SELECTION)) {
+            decorationStyle = pseudoStyle;
+
+            svgDecorationStyle = decorationStyle->svgStyle();
+            ASSERT(svgDecorationStyle);
+
+            if (!hasDecorationFill)
+                hasDecorationFill = svgDecorationStyle->hasFill();
+            if (!hasDecorationStroke)
+                hasDecorationStroke = svgDecorationStyle->hasStroke();
+        }
+    }
+
+    if (decorationStyle->visibility() == HIDDEN)
+        return;
+
+    if (hasDecorationFill) {
+        m_paintingResourceMode = ApplyToFillMode;
+        paintDecorationWithStyle(context, textOrigin, decorationStyle, decoration);
+    }
+
+    if (hasDecorationStroke) {
+        m_paintingResourceMode = ApplyToStrokeMode;
+        paintDecorationWithStyle(context, textOrigin, decorationStyle, decoration);
+    }
+}
+
+void SVGInlineTextBox::paintDecorationWithStyle(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* decorationStyle, ETextDecoration decoration)
+{
+    ASSERT(!m_paintingResource);
+    ASSERT(m_paintingResourceMode != ApplyToDefaultMode);
+    ASSERT(m_currentChunkPart.isValid());
+
+    const Font& font = decorationStyle->font();
+
+    // The initial y value refers to overline position.
+    float thickness = thicknessForDecoration(decoration, font);
+    float x = textOrigin.x();
+    float y = textOrigin.y() - font.ascent() + positionOffsetForDecoration(decoration, font, thickness);
+
+    context->save();
+    context->beginPath();
+    context->addPath(Path::createRectangle(FloatRect(x, y, m_currentChunkPart.width, thickness)));
+
+    if (acquirePaintingResource(context, decorationStyle))
+        releasePaintingResource(context);
+
+    context->restore();
+}
+
+void SVGInlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* style)
+{
+    // See if we have a selection to paint at all.
+    int startPos, endPos;
+    selectionStartEnd(startPos, endPos);
+    if (startPos >= endPos)
+        return;
+
+    Color backgroundColor = renderer()->selectionBackgroundColor();
+    if (!backgroundColor.isValid() || !backgroundColor.alpha())
+        return;
+
+    const Font& font = style->font();
+
+    FloatPoint selectionOrigin = textOrigin;
+    selectionOrigin.move(0, -font.ascent());
+
+    context->save();
+    context->setFillColor(backgroundColor, style->colorSpace());
+    context->fillRect(font.selectionRectForText(constructTextRun(style), selectionOrigin, m_currentChunkPart.height, startPos, endPos), backgroundColor, style->colorSpace());
+    context->restore();
+}
+
+void SVGInlineTextBox::paintTextWithShadows(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* style, TextRun& textRun, int startPos, int endPos)
+{
+    const Font& font = style->font();
+    const ShadowData* shadow = style->textShadow();
+
+    FloatRect shadowRect(FloatPoint(textOrigin.x(), textOrigin.y() - font.ascent()), FloatSize(m_currentChunkPart.width, m_currentChunkPart.height));
+    do {
+        if (!prepareGraphicsContextForTextPainting(context, textRun, style))
+            break;
+
+        FloatSize extraOffset;
+        if (shadow)
+            extraOffset = applyShadowToGraphicsContext(context, shadow, shadowRect, false /* stroked */, true /* opaque */);
+
+        font.drawText(context, textRun, textOrigin + extraOffset, startPos, endPos);
+        restoreGraphicsContextAfterTextPainting(context, textRun);
+
+        if (!shadow)
+            break;
+
+        if (shadow->next())
+            context->restore();
+        else
+            context->clearShadow();
+
+        shadow = shadow->next();
+    } while (shadow);
+}
+
+void SVGInlineTextBox::paintText(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* style, RenderStyle* selectionStyle, bool hasSelection, bool paintSelectedTextOnly)
+{
+    ASSERT(style);
+    ASSERT(selectionStyle);
+    ASSERT(m_currentChunkPart.isValid());
+
+    int startPos = 0;
+    int endPos = 0;
+    selectionStartEnd(startPos, endPos);
+
+    // Fast path if there is no selection, just draw the whole chunk part using the regular style
+    TextRun textRun(constructTextRun(style));
+    if (!hasSelection || startPos >= endPos) {
+        paintTextWithShadows(context, textOrigin, style, textRun, 0, m_currentChunkPart.length);
+        return;
+    }
+
+    // Eventually draw text using regular style until the start position of the selection
+    if (startPos > 0 && !paintSelectedTextOnly)
+        paintTextWithShadows(context, textOrigin, style, textRun, 0, startPos);
+
+    // Draw text using selection style from the start to the end position of the selection
+    TextRun selectionTextRun(constructTextRun(selectionStyle));
+    paintTextWithShadows(context, textOrigin, selectionStyle, textRun, startPos, endPos);
+
+    // Eventually draw text using regular style from the end position of the selection to the end of the current chunk part
+    if (endPos < m_currentChunkPart.length && !paintSelectedTextOnly)
+        paintTextWithShadows(context, textOrigin, style, textRun, endPos, m_currentChunkPart.length);
+}
+
+void SVGInlineTextBox::buildLayoutInformation(SVGCharacterLayoutInfo& info, SVGLastGlyphInfo& lastGlyph)
+{
+    RenderText* textRenderer = this->textRenderer();
+    ASSERT(textRenderer);
+
+    RenderStyle* style = textRenderer->style();
+    ASSERT(style);
+
+    const Font& font = style->font();
+    const UChar* characters = textRenderer->characters();
+
+    TextDirection textDirection = direction();
+    unsigned startPosition = start();
+    unsigned endPosition = end();
+    unsigned length = len();
+
+    const SVGRenderStyle* svgStyle = style->svgStyle();
+    bool isVerticalText = isVerticalWritingMode(svgStyle);
+
+    int charsConsumed = 0;
+    for (unsigned i = 0; i < length; i += charsConsumed) {
+        SVGChar svgChar;
+
+        if (info.inPathLayout())
+            svgChar.pathData = SVGCharOnPath::create();
+
+        float glyphWidth = 0.0f;
+        float glyphHeight = 0.0f;
+        String glyphName;
+        String unicodeString;
+        measureCharacter(style, i, charsConsumed, glyphName, unicodeString, glyphWidth, glyphHeight);
+
+        bool assignedX = false;
+        bool assignedY = false;
+
+        if (info.xValueAvailable() && (!info.inPathLayout() || (info.inPathLayout() && !isVerticalText))) {
+            if (!isVerticalText)
+                svgChar.newTextChunk = true;
+
+            assignedX = true;
+            svgChar.drawnSeperated = true;
+            info.curx = info.xValueNext();
+        }
+
+        if (info.yValueAvailable() && (!info.inPathLayout() || (info.inPathLayout() && isVerticalText))) {
+            if (isVerticalText)
+                svgChar.newTextChunk = true;
+
+            assignedY = true;
+            svgChar.drawnSeperated = true;
+            info.cury = info.yValueNext();
+        }
+
+        float dx = 0.0f;
+        float dy = 0.0f;
+
+        // Apply x-axis shift
+        if (info.dxValueAvailable()) {
+            svgChar.drawnSeperated = true;
+
+            dx = info.dxValueNext();
+            info.dx += dx;
+
+            if (!info.inPathLayout())
+                info.curx += dx;
+        }
+
+        // Apply y-axis shift
+        if (info.dyValueAvailable()) {
+            svgChar.drawnSeperated = true;
+
+            dy = info.dyValueNext();
+            info.dy += dy;
+
+            if (!info.inPathLayout())
+                info.cury += dy;
+        }
+
+        // Take letter & word spacing and kerning into account
+        float spacing = font.letterSpacing() + calculateCSSKerning(style);
+
+        const UChar* currentCharacter = characters + (textDirection == RTL ? endPosition - i : startPosition + i);
+        const UChar* lastCharacter = 0;
+
+        if (textDirection == RTL) {
+            if (i < endPosition)
+                lastCharacter = characters + endPosition - i +  1;
+        } else {
+            if (i > 0)
+                lastCharacter = characters + startPosition + i - 1;
+        }
+
+        // FIXME: SVG Kerning doesn't get applied on texts on path.
+        bool appliedSVGKerning = applySVGKerning(info, style, lastGlyph, unicodeString, glyphName, isVerticalText);
+        if (info.nextDrawnSeperated || spacing != 0.0f || appliedSVGKerning) {
+            info.nextDrawnSeperated = false;
+            svgChar.drawnSeperated = true;
+        }
+
+        if (currentCharacter && Font::treatAsSpace(*currentCharacter) && lastCharacter && !Font::treatAsSpace(*lastCharacter)) {
+            spacing += font.wordSpacing();
+
+            if (spacing != 0.0f && !info.inPathLayout())
+                info.nextDrawnSeperated = true;
+        }
+
+        float orientationAngle = glyphOrientationToAngle(svgStyle, isVerticalText, *currentCharacter);
+
+        float xOrientationShift = 0.0f;
+        float yOrientationShift = 0.0f;
+        float glyphAdvance = applyGlyphAdvanceAndShiftRespectingOrientation(isVerticalText, orientationAngle, glyphWidth, glyphHeight,
+                                                                            font, svgChar, xOrientationShift, yOrientationShift);
+
+        // Handle textPath layout mode
+        if (info.inPathLayout()) {
+            float extraAdvance = isVerticalText ? dy : dx;
+            float newOffset = FLT_MIN;
+
+            if (assignedX && !isVerticalText)
+                newOffset = info.curx;
+            else if (assignedY && isVerticalText)
+                newOffset = info.cury;
+
+            float correctedGlyphAdvance = glyphAdvance;
+
+            // Handle lengthAdjust="spacingAndGlyphs" by specifying per-character scale operations
+            if (info.pathTextLength && info.pathChunkLength) { 
+                if (isVerticalText) {
+                    svgChar.pathData->yScale = info.pathChunkLength / info.pathTextLength;
+                    spacing *= svgChar.pathData->yScale;
+                    correctedGlyphAdvance *= svgChar.pathData->yScale;
+                } else {
+                    svgChar.pathData->xScale = info.pathChunkLength / info.pathTextLength;
+                    spacing *= svgChar.pathData->xScale;
+                    correctedGlyphAdvance *= svgChar.pathData->xScale;
+                }
+            }
+
+            // Handle letter & word spacing on text path
+            float pathExtraAdvance = info.pathExtraAdvance;
+            info.pathExtraAdvance += spacing;
+
+            svgChar.pathData->hidden = !info.nextPathLayoutPointAndAngle(correctedGlyphAdvance, extraAdvance, newOffset);
+            svgChar.drawnSeperated = true;
+
+            info.pathExtraAdvance = pathExtraAdvance;
+        }
+
+        // Apply rotation
+        if (info.angleValueAvailable())
+            info.angle = info.angleValueNext();
+
+        // Apply baseline-shift
+        if (info.baselineShiftValueAvailable()) {
+            svgChar.drawnSeperated = true;
+            float shift = info.baselineShiftValueNext();
+
+            if (isVerticalText)
+                info.shiftx += shift;
+            else
+                info.shifty -= shift;
+        }
+
+        // Take dominant-baseline / alignment-baseline into account
+        yOrientationShift += alignmentBaselineToShift(isVerticalText, textRenderer, font);
+
+        svgChar.x = info.curx;
+        svgChar.y = info.cury;
+        svgChar.angle = info.angle;
+
+        // For text paths any shift (dx/dy/baseline-shift) has to be applied after the rotation
+        if (!info.inPathLayout()) {
+            svgChar.x += info.shiftx + xOrientationShift;
+            svgChar.y += info.shifty + yOrientationShift;
+
+            if (orientationAngle != 0.0f)
+                svgChar.angle += orientationAngle;
+
+            if (svgChar.angle != 0.0f)
+                svgChar.drawnSeperated = true;
+        } else {
+            svgChar.pathData->orientationAngle = orientationAngle;
+
+            if (isVerticalText)
+                svgChar.angle -= 90.0f;
+
+            svgChar.pathData->xShift = info.shiftx + xOrientationShift;
+            svgChar.pathData->yShift = info.shifty + yOrientationShift;
+
+            // Translate to glyph midpoint
+            if (isVerticalText) {
+                svgChar.pathData->xShift += info.dx;
+                svgChar.pathData->yShift -= glyphAdvance / 2.0f;
+            } else {
+                svgChar.pathData->xShift -= glyphAdvance / 2.0f;
+                svgChar.pathData->yShift += info.dy;
+            }
+        }
+
+        // Advance to new position
+        if (isVerticalText) {
+            svgChar.drawnSeperated = true;
+            info.cury += glyphAdvance + spacing;
+        } else
+            info.curx += glyphAdvance + spacing;
+
+        // Advance to next character group
+        for (int k = 0; k < charsConsumed; ++k) {
+            info.svgChars.append(svgChar);
+            info.processedSingleCharacter();
+            svgChar.drawnSeperated = false;
+            svgChar.newTextChunk = false;
+        }
+    }
+}
+
+FloatRect SVGInlineTextBox::calculateGlyphBoundaries(RenderStyle* style, int position, const SVGChar& character) const
+{
+    int charsConsumed = 0;
+    String glyphName;
+    String unicodeString;
+    float glyphWidth = 0.0f;
+    float glyphHeight = 0.0f;
+    measureCharacter(style, position, charsConsumed, glyphName, unicodeString, glyphWidth, glyphHeight);
+
+    FloatRect glyphRect(character.x, character.y - style->font().ascent(), glyphWidth, glyphHeight);
+    glyphRect = character.characterTransform().mapRect(glyphRect);
+    if (m_chunkTransformation.isIdentity())
+        return glyphRect;
+
+    return m_chunkTransformation.mapRect(glyphRect);
+}
+
+IntRect SVGInlineTextBox::calculateBoundaries() const
+{
+    FloatRect textRect;
+    int baseline = baselinePosition(true);
+
+    const Vector<SVGTextChunkPart>::const_iterator end = m_svgTextChunkParts.end();
+    for (Vector<SVGTextChunkPart>::const_iterator it = m_svgTextChunkParts.begin(); it != end; ++it)
+        textRect.unite(it->firstCharacter->characterTransform().mapRect(FloatRect(it->firstCharacter->x, it->firstCharacter->y - baseline, it->width, it->height)));
+
+    if (m_chunkTransformation.isIdentity())
+        return enclosingIntRect(textRect);
+
+    return enclosingIntRect(m_chunkTransformation.mapRect(textRect));
+}
+
+} // namespace WebCore
+
+#endif