WebCore/rendering/SVGInlineTextBox.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 04 Oct 2010 01:32:07 +0300
changeset 2 303757a437d3
parent 0 4f2f89ce4247
permissions -rw-r--r--
Revision: 201037 Kit: 201039

/**
 * 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