/*
 * This file is part of the WebKit project.
 *
 * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
 *           (C) 2006 Apple Computer Inc.
 *           (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 "wtf/Platform.h"

#if ENABLE(SVG)
#include "SVGRootInlineBox.h"

/*#include "Editor.h"
#include "Frame.h"
#include "GraphicsContext.h"*/
#include "RenderSVGRoot.h"
#include "SVGInlineFlowBox.h"
#include "SVGInlineTextBox.h"
#include "SVGFontElement.h"
#include "SVGPaintServer.h"
#include "SVGRenderStyleDefs.h"
#include "SVGRenderSupport.h"
#include "SVGResourceFilter.h"
#include "SVGTextPositioningElement.h"
#include "SVGURIReference.h"
//#include "Text.h"
//#include "UnicodeRange.h"

#include <float.h>
#include <math.h>

// Text chunk creation is complex and the whole process
// can easily be traced by setting this variable > 0.
#define DEBUG_CHUNK_BUILDING 0

namespace WebCore
{

static inline bool isVerticalWritingMode(const SVGRenderStyle *style)
{
    return style->writingMode() == WM_TBRL || style->writingMode() == WM_TB;
}

static inline EAlignmentBaseline dominantBaselineToShift(bool isVerticalText, const RenderObject *text, const Font &font)
{
    ASSERT(text);

    const SVGRenderStyle *style = text->style() ? text->style()->svgStyle() : nullptr;
    ASSERT(style);

    const SVGRenderStyle *parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : nullptr;

    EDominantBaseline baseline = style->dominantBaseline();
    if (baseline == DB_AUTO) {
        if (isVerticalText) {
            baseline = DB_CENTRAL;
        } else {
            baseline = DB_ALPHABETIC;
        }
    }

    switch (baseline) {
    case DB_USE_SCRIPT:
        // TODO: 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: {
        if (parentStyle) {
            return dominantBaselineToShift(isVerticalText, text->parent(), font);
        }

        ASSERT_NOT_REACHED();
        return AB_AUTO;
    }
    case DB_RESET_SIZE: {
        if (parentStyle) {
            return dominantBaselineToShift(isVerticalText, text->parent(), font);
        }

        ASSERT_NOT_REACHED();
        return AB_AUTO;
    }
    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;
    }
}

static inline float alignmentBaselineToShift(bool isVerticalText, const RenderObject *text, const Font &font)
{
    ASSERT(text);

    const SVGRenderStyle *style = text->style() ? text->style()->svgStyle() : nullptr;
    ASSERT(style);

    const SVGRenderStyle *parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : nullptr;

    EAlignmentBaseline baseline = style->alignmentBaseline();
    if (baseline == AB_AUTO) {
        if (parentStyle && style->dominantBaseline() == DB_AUTO) {
            baseline = dominantBaselineToShift(isVerticalText, text->parent(), font);
        } else {
            baseline = dominantBaselineToShift(isVerticalText, text, font);
        }

        ASSERT(baseline != AB_AUTO);
    }

    // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
    switch (baseline) {
    case AB_BASELINE: {
        if (parentStyle) {
            return dominantBaselineToShift(isVerticalText, text->parent(), font);
        }

        return 0.0f;
    }
    case AB_BEFORE_EDGE:
    case AB_TEXT_BEFORE_EDGE:
        return font.ascent();
    case AB_MIDDLE:
        return font.xHeight() / 2.0f;
    case AB_CENTRAL:
        // Not needed, we're taking this into account already for vertical text!
        // return (font.ascent() - font.descent()) / 2.0f;
        return 0.0f;
    case AB_AFTER_EDGE:
    case AB_TEXT_AFTER_EDGE:
    case AB_IDEOGRAPHIC:
        return font.descent();
    case AB_ALPHABETIC:
        return 0.0f;
    case AB_HANGING:
        return font.ascent() * 8.0f / 10.0f;
    case AB_MATHEMATICAL:
        return font.ascent() / 2.0f;
    default:
        ASSERT_NOT_REACHED();
        return 0.0f;
    }
}

static inline float glyphOrientationToAngle(const SVGRenderStyle *svgStyle, bool isVerticalText, const UChar &character)
{
    Q_UNUSED(character);
    switch (isVerticalText ? svgStyle->glyphOrientationVertical() : svgStyle->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.
        /*FIXME: khtml porting; unsigned int unicodeRange = findCharUnicodeRange(character);
        if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic)
            return 90.0f;*/

        return 0.0f;
    }
    case GO_90DEG:
        return 90.0f;
    case GO_180DEG:
        return 180.0f;
    case GO_270DEG:
        return 270.0f;
    case GO_0DEG:
    default:
        return 0.0f;
    }
}

static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle)
{
    return fabsf(fmodf(orientationAngle, 180.0f)) == 0.0f;
}

static inline float calculateGlyphAdvanceAndShiftRespectingOrientation(bool isVerticalText, float orientationAngle, float glyphWidth, float glyphHeight, const Font &font, SVGChar &svgChar, float &xOrientationShift, float &yOrientationShift)
{
    bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(orientationAngle);

    // 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) {
        if (orientationAngle == 0.0f) {
            xOrientationShift = -glyphWidth / 2.0f;
            yOrientationShift = font.ascent();
        } else if (orientationAngle == 90.0f) {
            xOrientationShift = -glyphHeight;
            yOrientationShift = font.descent();
            svgChar.orientationShiftY = -font.ascent();
        } else if (orientationAngle == 270.0f) {
            xOrientationShift = glyphHeight;
            yOrientationShift = font.descent();
            svgChar.orientationShiftX = -glyphWidth;
            svgChar.orientationShiftY = -font.ascent();
        } else if (orientationAngle == 180.0f) {
            yOrientationShift = font.ascent();
            svgChar.orientationShiftX = -glyphWidth / 2.0f;
            svgChar.orientationShiftY = font.ascent() - font.descent();
        }

        // vertical advance calculation
        if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) {
            return glyphWidth;
        }

        return glyphHeight;
    }

    // horizontal orientation handling
    if (orientationAngle == 90.0f) {
        xOrientationShift = glyphWidth / 2.0f;
        yOrientationShift = -font.descent();
        svgChar.orientationShiftX = -glyphWidth / 2.0f - font.descent();
        svgChar.orientationShiftY = font.descent();
    } else if (orientationAngle == 270.0f) {
        xOrientationShift = -glyphWidth / 2.0f;
        yOrientationShift = -font.descent();
        svgChar.orientationShiftX = -glyphWidth / 2.0f + font.descent();
        svgChar.orientationShiftY = glyphHeight;
    } else if (orientationAngle == 180.0f) {
        xOrientationShift = glyphWidth / 2.0f;
        svgChar.orientationShiftX = -glyphWidth / 2.0f;
        svgChar.orientationShiftY = font.ascent() - font.descent();
    }

    // horizontal advance calculation
    if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) {
        return glyphHeight;
    }

    return glyphWidth;
}

static inline void startTextChunk(SVGTextChunkLayoutInfo &info)
{
    info.chunk.boxes.clear();
    info.chunk.boxes.append(SVGInlineBoxCharacterRange());

    info.chunk.start = info.it;
    info.assignChunkProperties = true;
}

static inline void closeTextChunk(SVGTextChunkLayoutInfo &info)
{
    ASSERT(!info.chunk.boxes.last().isOpen());
    ASSERT(info.chunk.boxes.last().isClosed());

    info.chunk.end = info.it;
    ASSERT(info.chunk.end >= info.chunk.start);

    info.svgTextChunks.append(info.chunk);
}

RenderSVGRoot *findSVGRootObject(RenderObject *start)
{
    // Find associated root inline box
    while (start && !start->isSVGRoot()) {
        start = start->parent();
    }

    ASSERT(start);
    ASSERT(start->isSVGRoot());

    return static_cast<RenderSVGRoot *>(start);
}

static inline FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar> &chars)
{
    return topLeftPositionOfCharacterRange(chars.begin(), chars.end());
}

FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>::iterator it, Vector<SVGChar>::iterator end)
{
    float lowX = FLT_MAX, lowY = FLT_MAX;
    for (; it != end; ++it) {
        if (it->isHidden()) {
            continue;
        }

        float x = (*it).x;
        float y = (*it).y;

        if (x < lowX) {
            lowX = x;
        }

        if (y < lowY) {
            lowY = y;
        }
    }

    return FloatPoint(lowX, lowY);
}

/* FIXME
// Helper function
static float calculateKerning(RenderObject* item)
{
    Q_UNUSED(item);
    const Font& font = item->style()->font();
    const SVGRenderStyle* svgStyle = item->style()->svgStyle();

    float kerning = 0.0f;
    if (CSSPrimitiveValue* primitive = static_cast<CSSPrimitiveValue*>(svgStyle->kerning())) {
        kerning = primitive->getFloatValue();

        if (primitive->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE && font.pixelSize() > 0)
            kerning = kerning / 100.0f * font.pixelSize();
    }

    return kerning;
}
*/

// Helper class for paint()
struct SVGRootInlineBoxPaintWalker {
    SVGRootInlineBoxPaintWalker(SVGRootInlineBox *rootBox, SVGResourceFilter *rootFilter, RenderObject::PaintInfo paintInfo, int tx, int ty)
        : m_rootBox(rootBox)
        , m_chunkStarted(false)
        , m_paintInfo(paintInfo)
        , m_savedInfo(paintInfo)
        , m_boundingBox(tx + rootBox->xPos(), ty + rootBox->yPos(), rootBox->width(), rootBox->height())
        , m_filter(nullptr)
        , m_rootFilter(rootFilter)
        , m_fillPaintServer(nullptr)
        , m_strokePaintServer(nullptr)
        , m_fillPaintServerObject(nullptr)
        , m_strokePaintServerObject(nullptr)
        , m_tx(tx)
        , m_ty(ty)
    {
    }

    ~SVGRootInlineBoxPaintWalker()
    {
        ASSERT(!m_filter);
        ASSERT(!m_fillPaintServer);
        ASSERT(!m_fillPaintServerObject);
        ASSERT(!m_strokePaintServer);
        ASSERT(!m_strokePaintServerObject);
        ASSERT(!m_chunkStarted);
    }

    void teardownFillPaintServer()
    {
        if (!m_fillPaintServer) {
            return;
        }

        m_fillPaintServer->teardown(m_paintInfo.p, nullptr, m_fillPaintServerObject, ApplyToFillTargetType, true);

        m_fillPaintServer = nullptr;
        m_fillPaintServerObject = nullptr;
    }

    void teardownStrokePaintServer()
    {
        if (!m_strokePaintServer) {
            return;
        }

        m_strokePaintServer->teardown(m_paintInfo.p, nullptr, m_strokePaintServerObject, ApplyToStrokeTargetType, true);

        m_strokePaintServer = nullptr;
        m_strokePaintServerObject = nullptr;
    }

    void chunkStartCallback(InlineBox *box)
    {
        ASSERT(!m_chunkStarted);
        m_chunkStarted = true;

        InlineFlowBox *flowBox = box->parent();

        // Initialize text rendering
        RenderObject *object = flowBox->object();
        ASSERT(object);

        m_savedInfo = m_paintInfo;
        //m_paintInfo.context->save();

        if (!flowBox->isRootInlineBox()) {
            ;//FIXME m_paintInfo.context->concatCTM(m_rootBox->object()->localTransform());
        }

        //m_paintInfo.context->concatCTM(object->localTransform());
        m_paintInfo.p->setWorldMatrix(object->localTransform(), true);

        if (!flowBox->isRootInlineBox()) {
            prepareToRenderSVGContent(object, m_paintInfo, m_boundingBox, m_filter, m_rootFilter);
            // FIXME khtml m_paintInfo.rect = object->localTransform().inverse().mapRect(m_paintInfo.rect);
        }
    }

    void chunkEndCallback(InlineBox *box)
    {
        ASSERT(m_chunkStarted);
        m_chunkStarted = false;

        InlineFlowBox *flowBox = box->parent();

        RenderObject *object = flowBox->object();
        ASSERT(object);

        // Clean up last used paint server
        teardownFillPaintServer();
        teardownStrokePaintServer();

        // Finalize text rendering
        if (!flowBox->isRootInlineBox()) {
            //FIXME khtml finishRenderSVGContent(object, m_paintInfo, m_boundingBox, m_filter, m_savedInfo.context);
            m_filter = nullptr;
        }

        // Restore context & repaint rect
        //FIXME m_paintInfo.context->restore();
        //FIXME m_paintInfo.rect = m_savedInfo.rect;
    }

    bool chunkSetupFillCallback(InlineBox *box)
    {
        InlineFlowBox *flowBox = box->parent();

        // Setup fill paint server
        RenderObject *object = flowBox->object();
        ASSERT(object);

        ASSERT(!m_strokePaintServer);
        teardownFillPaintServer();

        m_fillPaintServer = SVGPaintServer::fillPaintServer(object->style(), object);
        if (m_fillPaintServer) {
            m_fillPaintServer->setup(m_paintInfo.p, nullptr, object, ApplyToFillTargetType, true);
            m_fillPaintServerObject = object;
            return true;
        }

        return false;
    }

    bool chunkSetupStrokeCallback(InlineBox *box)
    {
        InlineFlowBox *flowBox = box->parent();

        // Setup stroke paint server
        RenderObject *object = flowBox->object();
        ASSERT(object);

        // If we're both stroked & filled, teardown fill paint server before stroking.
        teardownFillPaintServer();
        teardownStrokePaintServer();

        m_strokePaintServer = SVGPaintServer::strokePaintServer(object->style(), object);

        if (m_strokePaintServer) {
            m_strokePaintServer->setup(m_paintInfo.p, nullptr, object, ApplyToStrokeTargetType, true);
            m_strokePaintServerObject = object;
            return true;
        }

        return false;
    }

    void chunkPortionCallback(SVGInlineTextBox *textBox, int startOffset, const AffineTransform &chunkCtm,
                              const Vector<SVGChar>::iterator &start, const Vector<SVGChar>::iterator &end)
    {
        Q_UNUSED(chunkCtm);
        //qDebug() << "text chunk rendering code here";
        RenderText *text = textBox->/*textObject()*/renderText();
        ASSERT(text);

        RenderStyle *styleToUse = text->style(/*textBox->isFirstLineStyle()*/);
        ASSERT(styleToUse);

        startOffset += textBox->start();

        int textDecorations = styleToUse->textDecorationsInEffect();

        // khtml int textWidth = 0;
        IntPoint decorationOrigin;
        SVGTextDecorationInfo info;

        /*FIXME khtml if (!chunkCtm.isIdentity())
            m_paintInfo.context->concatCTM(chunkCtm);*/

        for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
            if (it->isHidden()) {
                continue;
            }

            // Determine how many characters - starting from the current - can be drawn at once.
            Vector<SVGChar>::iterator itSearch = it + 1;
            while (itSearch != end) {
                if (itSearch->drawnSeperated || itSearch->isHidden()) {
                    break;
                }

                itSearch++;
            }

            /*FIXME khtmlconst*/ UChar *stringStart = text->text() + startOffset + (it - start);
            unsigned int stringLength = itSearch - it;

            // Paint decorations, that have to be drawn before the text gets drawn
            if (textDecorations != TDNONE /*FIXME khtml && m_paintInfo.phase != PaintPhaseSelection*/) {
                //khtml textWidth = styleToUse->font().width(svgTextRunForInlineTextBox(stringStart, stringLength, styleToUse, textBox, (*it).x));
                //FIXME khtml textWidth = styleToUse->htmlFont().width(stringStart, stringLength, 0, stringLength, false /*fast algo*/);
                decorationOrigin = IntPoint((int)(*it).x, (int)(*it).y - styleToUse->htmlFont().ascent());
                info = m_rootBox->retrievePaintServersForTextDecoration(text);
            }

            /*if (textDecorations & UNDERLINE && textWidth != 0.0f)
                textBox->paintDecoration(UNDERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);

            if (textDecorations & OVERLINE && textWidth != 0.0f)
                textBox->paintDecoration(OVERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);*/

            // Paint text
            SVGPaintServer *activePaintServer = m_fillPaintServer;
            if (!activePaintServer) {
                activePaintServer = m_strokePaintServer;
            }

            ASSERT(activePaintServer);
            textBox->paintCharacters(m_paintInfo, m_tx, m_ty, *it, stringStart, stringLength, activePaintServer);

            // Paint decorations, that have to be drawn afterwards
            /* FIXME khtml if (textDecorations & LINE_THROUGH && textWidth != 0.0f)
                textBox->paintDecoration(LINE_THROUGH, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);*/

            // Skip processed characters
            it = itSearch - 1;
        }

        /* FIXME khtml if (!chunkCtm.isIdentity())
            m_paintInfo.context->concatCTM(chunkCtm.inverse());*/
    }

private:
    SVGRootInlineBox *m_rootBox;
    bool m_chunkStarted : 1;

    RenderObject::PaintInfo m_paintInfo;
    RenderObject::PaintInfo m_savedInfo;

    FloatRect m_boundingBox;
    SVGResourceFilter *m_filter;
    SVGResourceFilter *m_rootFilter;

    SVGPaintServer *m_fillPaintServer;
    SVGPaintServer *m_strokePaintServer;

    RenderObject *m_fillPaintServerObject;
    RenderObject *m_strokePaintServerObject;

    int m_tx;
    int m_ty;
};

void SVGRootInlineBox::paint(RenderObject::PaintInfo &paintInfo, int tx, int ty)
{
    //if (paintInfo.context->paintingDisabled() || paintInfo.phase != PaintPhaseForeground)
    //    return;

    RenderObject::PaintInfo savedInfo(paintInfo);
    //paintInfo.context->save();

    SVGResourceFilter *filter = nullptr;
    FloatRect boundingBox(tx + xPos(), ty + yPos(), width(), height());

    // Initialize text rendering
    paintInfo.p->setWorldMatrix(object()->localTransform(), true);
    prepareToRenderSVGContent(object(), paintInfo, boundingBox, filter);
    paintInfo.p->setWorldMatrix(object()->localTransform().inverse(), true);

    //qDebug() << "paint at" << tx << ty;
    //qDebug() << "pos: (" << (tx + xPos()) << "," << (ty + yPos()) << ")";
    //qDebug() << "size: " << width() << "x" << height();

    // Render text, chunk-by-chunk
    SVGRootInlineBoxPaintWalker walkerCallback(this, filter, paintInfo, tx, ty);
    SVGTextChunkWalker<SVGRootInlineBoxPaintWalker> walker(&walkerCallback,
            &SVGRootInlineBoxPaintWalker::chunkPortionCallback,
            &SVGRootInlineBoxPaintWalker::chunkStartCallback,
            &SVGRootInlineBoxPaintWalker::chunkEndCallback,
            &SVGRootInlineBoxPaintWalker::chunkSetupFillCallback,
            &SVGRootInlineBoxPaintWalker::chunkSetupStrokeCallback);

    walkTextChunks(&walker);

    // Finalize text rendering
    //FIXME khtml finishRenderSVGContent(object(), paintInfo, boundingBox, filter, savedInfo.context);
    //paintInfo.context->restore();
}

int SVGRootInlineBox::placeBoxesHorizontally(int, int &leftPosition, int &rightPosition, bool &)
{
    // Remove any offsets caused by RTL text layout
    leftPosition = 0;
    rightPosition = 0;
    return 0;
}

void SVGRootInlineBox::verticallyAlignBoxes(int &heightOfBlock)
{
    // height is set by layoutInlineBoxes.
    heightOfBlock = height();
}

float cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange &range)
{
    ASSERT(!range.isOpen());
    ASSERT(range.isClosed());
    ASSERT(range.box->isInlineTextBox());

    InlineTextBox *textBox = static_cast<InlineTextBox *>(range.box);
    RenderText *text = textBox->renderText();
    RenderStyle *style = text->style();

    return style->htmlFont().floatWidth(text->text(), textBox->start() + range.startOffset,
                                        range.endOffset - range.startOffset);
}

float cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange &range)
{
    ASSERT(!range.isOpen());
    ASSERT(range.isClosed());
    ASSERT(range.box->isInlineTextBox());

    InlineTextBox *textBox = static_cast<InlineTextBox *>(range.box);
    RenderText *text = textBox->renderText();
    const khtml::Font &font = text->style()->htmlFont();
    return (range.endOffset - range.startOffset) * (font.ascent() + font.descent());
    return 0;
}

/*TextRun svgTextRunForInlineTextBox(const UChar* c, int len, RenderStyle* style, const InlineTextBox* textBox, float xPos)
{
    ASSERT(textBox);
    ASSERT(style);

    TextRun run(c, len, false, static_cast<int>(xPos), textBox->toAdd(), textBox->m_reversed, textBox->m_dirOverride || style->visuallyOrdered());

#if ENABLE(SVG_FONTS)
    run.setReferencingRenderObject(textBox->textObject()->parent());
#endif

    // We handle letter & word spacing ourselves
    run.disableSpacing();
    return run;
}*/

static float cummulatedWidthOrHeightOfTextChunk(SVGTextChunk &chunk, bool calcWidthOnly)
{
    float length = 0.0f;
    Vector<SVGChar>::iterator charIt = chunk.start;

    Vector<SVGInlineBoxCharacterRange>::iterator it = chunk.boxes.begin();
    Vector<SVGInlineBoxCharacterRange>::iterator end = chunk.boxes.end();

    for (; it != end; ++it) {
        SVGInlineBoxCharacterRange &range = *it;
        // qDebug() << "box range:" << range.startOffset << range.endOffset;

        SVGInlineTextBox *box = static_cast<SVGInlineTextBox *>(range.box);
        RenderStyle *style = box->object()->style();

        for (int i = range.startOffset; i < range.endOffset; ++i) {
            ASSERT(charIt <= chunk.end);

            // Determine how many characters - starting from the current - can be measured at once.
            // Important for non-absolute positioned non-latin1 text (ie. Arabic) where ie. the width
            // of a string is not the sum of the boundaries of all contained glyphs.
            Vector<SVGChar>::iterator itSearch = charIt + 1;
            Vector<SVGChar>::iterator endSearch = charIt + range.endOffset - i;
            while (itSearch != endSearch) {
                // No need to check for 'isHidden()' here as this function is not called for text paths.
                if (itSearch->drawnSeperated) {
                    break;
                }

                itSearch++;
            }

            unsigned int positionOffset = itSearch - charIt;

            // Calculate width/height of subrange
            SVGInlineBoxCharacterRange subRange;
            subRange.box = range.box;
            subRange.startOffset = i;
            subRange.endOffset = i + positionOffset;
            // qDebug() << "got subrange:" << subRange.startOffset << subRange.endOffset;

            if (calcWidthOnly) {
                length += cummulatedWidthOfInlineBoxCharacterRange(subRange);
            } else {
                length += cummulatedHeightOfInlineBoxCharacterRange(subRange);
            }

            // Calculate gap between the previous & current range
            // <text x="10 50 70">ABCD</text> - we need to take the gaps between A & B into account
            // so add "40" as width, and analogous for B & C, add "20" as width.
            if (itSearch > chunk.start && itSearch < chunk.end) {
                SVGChar &lastCharacter = *(itSearch - 1);
                SVGChar &currentCharacter = *itSearch;

                int offset = box->m_reversed ? box->end() - i - positionOffset + 1 : box->start() + i + positionOffset - 1;

                // FIXME: does this need to change to handle multichar glyphs?
                int charsConsumed = 1;
                String glyphName;
                if (calcWidthOnly) {
                    float lastGlyphWidth = box->calculateGlyphWidth(style, offset, 0, charsConsumed, glyphName);
                    length += currentCharacter.x - lastCharacter.x - lastGlyphWidth;
                } else {
                    float lastGlyphHeight = box->calculateGlyphHeight(style, offset, 0);
                    length += currentCharacter.y - lastCharacter.y - lastGlyphHeight;
                }
            }

            // Advance processed characters
            i += positionOffset - 1;
            charIt = itSearch;
        }
    }

    ASSERT(charIt == chunk.end);
    return length;
}

static float cummulatedWidthOfTextChunk(SVGTextChunk &chunk)
{
    return cummulatedWidthOrHeightOfTextChunk(chunk, true);
}

static float cummulatedHeightOfTextChunk(SVGTextChunk &chunk)
{
    return cummulatedWidthOrHeightOfTextChunk(chunk, false);
}

static float calculateTextAnchorShiftForTextChunk(SVGTextChunk &chunk, ETextAnchor anchor)
{
    float shift = 0.0f;

    if (chunk.isVerticalText) {
        shift = cummulatedHeightOfTextChunk(chunk);
    } else {
        shift = cummulatedWidthOfTextChunk(chunk);
    }

    // qDebug() << anchor << shift << TA_MIDDLE;

    if (anchor == TA_MIDDLE) {
        shift *= -0.5f;
    } else {
        shift *= -1.0f;
    }

    return shift;
}

static void applyTextAnchorToTextChunk(SVGTextChunk &chunk)
{
    // This method is not called for chunks containing chars aligned on a path.
    // -> all characters are visible, no need to check for "isHidden()" anywhere.

    if (chunk.anchor == TA_START) {
        return;
    }

    float shift = calculateTextAnchorShiftForTextChunk(chunk, chunk.anchor);

    // Apply correction to chunk
    Vector<SVGChar>::iterator chunkIt = chunk.start;
    for (; chunkIt != chunk.end; ++chunkIt) {
        SVGChar &curChar = *chunkIt;

        if (chunk.isVerticalText) {
            curChar.y += shift;
        } else {
            curChar.x += shift;
        }
    }

    // Move inline boxes
    Vector<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin();
    Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = chunk.boxes.end();

    for (; boxIt != boxEnd; ++boxIt) {
        SVGInlineBoxCharacterRange &range = *boxIt;

        InlineBox *curBox = range.box;
        ASSERT(curBox->isInlineTextBox());
        ASSERT(curBox->parent() && (curBox->parent()->isRootInlineBox() || curBox->parent()->isInlineFlowBox()));

        // Move target box
        if (chunk.isVerticalText) {
            curBox->setYPos(curBox->yPos() + static_cast<int>(shift));
        } else {
            curBox->setXPos(curBox->xPos() + static_cast<int>(shift));
        }
    }
}

static float calculateTextLengthCorrectionForTextChunk(SVGTextChunk &chunk, ELengthAdjust lengthAdjust, float &computedLength)
{
    //qDebug() << "text length";
    if (chunk.textLength <= 0.0f) {
        return 0.0f;
    }

    float computedWidth = cummulatedWidthOfTextChunk(chunk);
    float computedHeight = cummulatedHeightOfTextChunk(chunk);

    if ((computedWidth <= 0.0f && !chunk.isVerticalText) ||
            (computedHeight <= 0.0f && chunk.isVerticalText)) {
        return 0.0f;
    }

    if (chunk.isVerticalText) {
        computedLength = computedHeight;
    } else {
        computedLength = computedWidth;
    }

    if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) {
        if (chunk.isVerticalText) {
            chunk.ctm.scale(1.0f, chunk.textLength / computedLength);
        } else {
            chunk.ctm.scale(chunk.textLength / computedLength, 1.0f);
        }

        return 0.0f;
    }

    return (chunk.textLength - computedLength) / float(chunk.end - chunk.start);
}

static void applyTextLengthCorrectionToTextChunk(SVGTextChunk &chunk)
{
    // This method is not called for chunks containing chars aligned on a path.
    // -> all characters are visible, no need to check for "isHidden()" anywhere.

    // lengthAdjust="spacingAndGlyphs" is handled by modifying chunk.ctm
    float computedLength = 0.0f;
    float spacingToApply = calculateTextLengthCorrectionForTextChunk(chunk, chunk.lengthAdjust, computedLength);

    if (!chunk.ctm.isIdentity() && chunk.lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) {
        SVGChar &firstChar = *(chunk.start);

        // Assure we apply the chunk scaling in the right origin
        AffineTransform newChunkCtm;
        newChunkCtm.translate(firstChar.x, firstChar.y);
        newChunkCtm = chunk.ctm * newChunkCtm;
        newChunkCtm.translate(-firstChar.x, -firstChar.y);

        chunk.ctm = newChunkCtm;
    }

    // Apply correction to chunk
    if (spacingToApply != 0.0f) {
        Vector<SVGChar>::iterator chunkIt = chunk.start;
        for (; chunkIt != chunk.end; ++chunkIt) {
            SVGChar &curChar = *chunkIt;
            curChar.drawnSeperated = true;

            if (chunk.isVerticalText) {
                curChar.y += (chunkIt - chunk.start) * spacingToApply;
            } else {
                curChar.x += (chunkIt - chunk.start) * spacingToApply;
            }
        }
    }
}

void SVGRootInlineBox::computePerCharacterLayoutInformation()
{
    //qDebug() << "computePerCharacterLayoutInformation()";
    // Clean up any previous layout information
    m_svgChars.clear();
    m_svgTextChunks.clear();

    // Build layout information for all contained render objects
    SVGCharacterLayoutInfo info(m_svgChars);
    //qDebug() << "before build layout info";
    buildLayoutInformation(this, info);
    //qDebug() << "after build layout info";

    // Now all layout information are available for every character
    // contained in any of our child inline/flow boxes. Build list
    // of text chunks now, to be able to apply text-anchor shifts.
    buildTextChunks(m_svgChars, m_svgTextChunks, this);
    //qDebug() << "after build text chunks";

    // Layout all text chunks
    // text-anchor needs to be applied to individual chunks.
    layoutTextChunks();
    //qDebug() << "after layout text chunks";

    // Finally the top left position of our box is known.
    // Propagate this knownledge to our RenderSVGText parent.
    FloatPoint topLeft = topLeftPositionOfCharacterRange(m_svgChars);
    object()->setPos((int) floorf(topLeft.x()), (int) floorf(topLeft.y()));

    // Layout all InlineText/Flow boxes
    // BEWARE: This requires the root top/left position to be set correctly before!
    //qDebug() << "before layout inline boxes";
    layoutInlineBoxes();
    //qDebug() << "at the end";
}

void SVGRootInlineBox::buildLayoutInformation(InlineFlowBox *start, SVGCharacterLayoutInfo &info)
{
    if (start->isRootInlineBox()) {
        ASSERT(start->object()->element()->hasTagName(SVGNames::textTag));

        SVGTextPositioningElement *positioningElement = static_cast<SVGTextPositioningElement *>(start->object()->element());
        ASSERT(positioningElement);
        ASSERT(positioningElement->parentNode());

        info.addLayoutInformation(positioningElement);
    }

    LastGlyphInfo lastGlyph;

    for (InlineBox *curr = start->firstChild(); curr; curr = curr->nextOnLine()) {
        if (curr->object()->isText()) {
            buildLayoutInformationForTextBox(info, static_cast<InlineTextBox *>(curr), lastGlyph);
        } else {
            ASSERT(curr->isInlineFlowBox());
            InlineFlowBox *flowBox = static_cast<InlineFlowBox *>(curr);

            bool isAnchor = flowBox->object()->element()->hasTagName(SVGNames::aTag);
            bool isTextPath = flowBox->object()->element()->hasTagName(SVGNames::textPathTag);

            if (!isTextPath && !isAnchor) {
                SVGTextPositioningElement *positioningElement = static_cast<SVGTextPositioningElement *>(flowBox->object()->element());
                ASSERT(positioningElement);
                ASSERT(positioningElement->parentNode());

                info.addLayoutInformation(positioningElement);
            } else if (!isAnchor) {
                info.setInPathLayout(true);

                // Handle text-anchor/textLength on path, which is special.
                SVGTextContentElement *textContent = nullptr;
                Node *node = flowBox->object()->element();
                if (node && node->isSVGElement()) {
                    textContent = static_cast<SVGTextContentElement *>(node);
                }
                ASSERT(textContent);

                ELengthAdjust lengthAdjust = (ELengthAdjust) textContent->lengthAdjust();
                ETextAnchor anchor = flowBox->object()->style()->svgStyle()->textAnchor();
                float textAnchorStartOffset = 0.0f;

                // Initialize sub-layout. We need to create text chunks from the textPath
                // children using our standard layout code, to be able to measure the
                // text length using our normal methods and not textPath specific hacks.
                Vector<SVGChar> tempChars;
                Vector<SVGTextChunk> tempChunks;

                SVGCharacterLayoutInfo tempInfo(tempChars);
                buildLayoutInformation(flowBox, tempInfo);

                buildTextChunks(tempChars, tempChunks, flowBox);

                Vector<SVGTextChunk>::iterator it = tempChunks.begin();
                Vector<SVGTextChunk>::iterator end = tempChunks.end();

                AffineTransform ctm;
                float computedLength = 0.0f;

                for (; it != end; ++it) {
                    SVGTextChunk &chunk = *it;

                    // Apply text-length calculation
                    info.pathExtraAdvance += calculateTextLengthCorrectionForTextChunk(chunk, lengthAdjust, computedLength);

                    if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) {
                        info.pathTextLength += computedLength;
                        info.pathChunkLength += chunk.textLength;
                    }

                    // Calculate text-anchor start offset
                    if (anchor == TA_START) {
                        continue;
                    }

                    textAnchorStartOffset += calculateTextAnchorShiftForTextChunk(chunk, anchor);
                }

                info.addLayoutInformation(flowBox, textAnchorStartOffset);
            }

            float shiftxSaved = info.shiftx;
            float shiftySaved = info.shifty;

            buildLayoutInformation(flowBox, info);
            info.processedChunk(shiftxSaved, shiftySaved);

            if (isTextPath) {
                info.setInPathLayout(false);
            }
        }
    }
}

void SVGRootInlineBox::layoutInlineBoxes()
{
    int lowX = INT_MAX;
    int lowY = INT_MAX;
    int highX = INT_MIN;
    int highY = INT_MIN;

    // Layout all child boxes
    Vector<SVGChar>::iterator it = m_svgChars.begin();
    layoutInlineBoxes(this, it, lowX, highX, lowY, highY);
    ASSERT(it == m_svgChars.end());
}

void SVGRootInlineBox::layoutInlineBoxes(InlineFlowBox *start, Vector<SVGChar>::iterator &it, int &lowX, int &highX, int &lowY, int &highY)
{
    for (InlineBox *curr = start->firstChild(); curr; curr = curr->nextOnLine()) {
        RenderStyle *style = curr->object()->style();
        const Font &font = style->htmlFont();

        if (curr->object()->isText()) {
            SVGInlineTextBox *textBox = static_cast<SVGInlineTextBox *>(curr);
            unsigned length = textBox->len();

            SVGChar curChar = *it;
            ASSERT(it != m_svgChars.end());

            FloatRect stringRect;
            for (unsigned i = 0; i < length; ++i) {
                ASSERT(it != m_svgChars.end());

                if (it->isHidden()) {
                    ++it;
                    continue;
                }

                stringRect.unite(textBox->calculateGlyphBoundaries(style, textBox->start() + i, *it));
                ++it;
            }

            IntRect enclosedStringRect = enclosingIntRect(stringRect);

            int minX = enclosedStringRect.x();
            int maxX = minX + enclosedStringRect.width();

            int minY = enclosedStringRect.y();
            int maxY = minY + enclosedStringRect.height();

            curr->setXPos(minX - object()->xPos());
            curr->setWidth(enclosedStringRect.width());

            curr->setYPos(minY - object()->yPos());
            curr->setBaseline(font.ascent());
            curr->setHeight(enclosedStringRect.height());

            if (minX < lowX) {
                lowX = minX;
            }

            if (maxX > highX) {
                highX = maxX;
            }

            if (minY < lowY) {
                lowY = minY;
            }

            if (maxY > highY) {
                highY = maxY;
            }
        } else {
            ASSERT(curr->isInlineFlowBox());

            int minX = INT_MAX;
            int minY = INT_MAX;
            int maxX = INT_MIN;
            int maxY = INT_MIN;

            InlineFlowBox *flowBox = static_cast<InlineFlowBox *>(curr);
            layoutInlineBoxes(flowBox, it, minX, maxX, minY, maxY);

            curr->setXPos(minX - object()->xPos());
            curr->setWidth(maxX - minX);

            curr->setYPos(minY - object()->yPos());
            curr->setBaseline(font.ascent());
            curr->setHeight(maxY - minY);

            if (minX < lowX) {
                lowX = minX;
            }

            if (maxX > highX) {
                highX = maxX;
            }

            if (minY < lowY) {
                lowY = minY;
            }

            if (maxY > highY) {
                highY = maxY;
            }
        }
    }

    if (start->isRootInlineBox()) {
        int top = lowY - object()->yPos();
        //int bottom = highY - object()->yPos();

        start->setXPos(lowX - object()->xPos());
        start->setYPos(top);

        start->setWidth(highX - lowX);
        start->setHeight(highY - lowY);

        /*FIXME start->setVerticalOverflowPositions(top, bottom);
        start->setVerticalSelectionPositions(top, bottom);*/
    }
}

void SVGRootInlineBox::buildLayoutInformationForTextBox(SVGCharacterLayoutInfo &info, InlineTextBox *textBox, LastGlyphInfo &lastGlyph)
{
    Q_UNUSED(lastGlyph);

    RenderText *text = textBox->renderText();
    ASSERT(text);

    RenderStyle *style = text->style(/*textBox->isFirstLineStyle()*/);
    ASSERT(style);

    const Font &font = style->htmlFont();
    SVGInlineTextBox *svgTextBox = static_cast<SVGInlineTextBox *>(textBox);

    unsigned length = textBox->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;

        int extraCharsAvailable = length - i - 1;

        String unicodeStr;
        String glyphName;
        if (textBox->m_reversed) {
            glyphWidth = svgTextBox->calculateGlyphWidth(style, textBox->end() - i, extraCharsAvailable, charsConsumed, glyphName);
            glyphHeight = svgTextBox->calculateGlyphHeight(style, textBox->end() - i, extraCharsAvailable);
            unicodeStr = String(textBox->renderText()->text()/*->characters()*/ + textBox->end() - i, charsConsumed);
        } else {
            glyphWidth = svgTextBox->calculateGlyphWidth(style, textBox->start() + i, extraCharsAvailable, charsConsumed, glyphName);
            glyphHeight = svgTextBox->calculateGlyphHeight(style, textBox->start() + i, extraCharsAvailable);
            unicodeStr = String(textBox->renderText()->text()/*->characters()*/ + textBox->start() + i, charsConsumed);
        }

        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 = 0;//FIXME font.letterSpacing() + calculateKerning(textBox->object()->element()->renderer());

        const UChar *currentCharacter = text->text()/*->characters()*/ + (textBox->m_reversed ? textBox->end() - i : textBox->start() + i);
#if 0 // FIXME (see below)
        const UChar *lastCharacter = 0;

        if (textBox->m_reversed) {
            if (i < textBox->end()) {
                lastCharacter = text->text()/*characters()*/ + textBox->end() - i +  1;
            }
        } else {
            if (i > 0) {
                lastCharacter = text->text()/*characters()*/ + textBox->start() + i - 1;
            }
        }
#endif

        if (info.nextDrawnSeperated || spacing != 0.0f) {
            info.nextDrawnSeperated = false;
            svgChar.drawnSeperated = true;
        }

        /*FIXME 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 = calculateGlyphAdvanceAndShiftRespectingOrientation(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 > 0.0f && info.pathChunkLength > 0.0f) {
                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, text, 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;
            }
        }

        double kerning = 0.0;
#if ENABLE(SVG_FONTS)
        /*FIXME khtml SVGFontElement* svgFont = 0;
        if (style->font().isSVGFont())
            svgFont = style->font().svgFont();

        if (lastGlyph.isValid && style->font().isSVGFont()) {
            SVGHorizontalKerningPair kerningPair;
            if (svgFont->getHorizontalKerningPairForStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeStr, glyphName, kerningPair))
                kerning = kerningPair.kerning;
        }

        if (style->font().isSVGFont()) {
            lastGlyph.unicode = unicodeStr;
            lastGlyph.glyphName = glyphName;
            lastGlyph.isValid = true;
        } else
            lastGlyph.isValid = false;*/
#endif

        svgChar.x -= (float)kerning;

        // Advance to new position
        if (isVerticalText) {
            svgChar.drawnSeperated = true;
            info.cury += glyphAdvance + spacing;
        } else {
            info.curx += glyphAdvance + spacing - (float)kerning;
        }

        // Advance to next character group
        for (int k = 0; k < charsConsumed; ++k) {
            info.svgChars.append(svgChar);
            info.processedSingleCharacter();
            svgChar.drawnSeperated = false;
            svgChar.newTextChunk = false;
        }
    }
}

void SVGRootInlineBox::buildTextChunks(Vector<SVGChar> &svgChars, Vector<SVGTextChunk> &svgTextChunks, InlineFlowBox *start)
{
    SVGTextChunkLayoutInfo info(svgTextChunks);
    info.it = svgChars.begin();
    info.chunk.start = svgChars.begin();
    info.chunk.end = svgChars.begin();

    buildTextChunks(svgChars, start, info);
    ASSERT(info.it == svgChars.end());
}

void SVGRootInlineBox::buildTextChunks(Vector<SVGChar> &svgChars, InlineFlowBox *start, SVGTextChunkLayoutInfo &info)
{
#if DEBUG_CHUNK_BUILDING > 1
    fprintf(stderr, " -> buildTextChunks(start=%p)\n", start);
#endif

    for (InlineBox *curr = start->firstChild(); curr; curr = curr->nextOnLine()) {
        if (curr->object()->isText()) {
            InlineTextBox *textBox = static_cast<InlineTextBox *>(curr);

            unsigned length = textBox->len();
            if (!length) {
                continue;
            }

#if DEBUG_CHUNK_BUILDING > 1
            fprintf(stderr, " -> Handle inline text box (%p) with %i characters (start: %i, end: %i), handlingTextPath=%i\n",
                    textBox, length, textBox->start(), textBox->end(), (int) info.handlingTextPath);
#endif

            RenderText *text = textBox->renderText();
            ASSERT(text);
            ASSERT(text->element());

            SVGTextContentElement *textContent = nullptr;
            Node *node = text->element()->parent();
            if (node && node->isSVGElement()) {
                textContent = static_cast<SVGTextContentElement *>(node);
            }
            ASSERT(textContent);

            // Start new character range for the first chunk
            bool isFirstCharacter = info.svgTextChunks.isEmpty() && info.chunk.start == info.it && info.chunk.start == info.chunk.end;
            if (isFirstCharacter) {
                ASSERT(info.chunk.boxes.isEmpty());
                info.chunk.boxes.append(SVGInlineBoxCharacterRange());
            } else {
                ASSERT(!info.chunk.boxes.isEmpty());
            }

            // Walk string to find out new chunk positions, if existent
            for (unsigned i = 0; i < length; ++i) {
                ASSERT(info.it != svgChars.end());

                SVGInlineBoxCharacterRange &range = info.chunk.boxes.last();
                if (range.isOpen()) {
                    range.box = curr;
                    range.startOffset = (i == 0 ? 0 : i - 1);

#if DEBUG_CHUNK_BUILDING > 1
                    fprintf(stderr, " | -> Range is open! box=%p, startOffset=%i\n", range.box, range.startOffset);
#endif
                }

                // If a new (or the first) chunk has been started, record it's text-anchor and writing mode.
                if (info.assignChunkProperties) {
                    info.assignChunkProperties = false;

                    info.chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle());
                    info.chunk.isTextPath = info.handlingTextPath;
                    info.chunk.anchor = text->style()->svgStyle()->textAnchor();
                    info.chunk.textLength = textContent->textLength().value();
                    info.chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust();

#if DEBUG_CHUNK_BUILDING > 1
                    fprintf(stderr, " | -> Assign chunk properties, isVerticalText=%i, anchor=%i\n", info.chunk.isVerticalText, info.chunk.anchor);
#endif
                }

                if (i > 0 && !isFirstCharacter && (*info.it).newTextChunk) {
                    // Close mid chunk & character range
                    ASSERT(!range.isOpen());
                    ASSERT(!range.isClosed());

                    range.endOffset = i;
                    closeTextChunk(info);

#if DEBUG_CHUNK_BUILDING > 1
                    fprintf(stderr, " | -> Close mid-text chunk, at endOffset: %i and starting new mid chunk!\n", range.endOffset);
#endif

                    // Prepare for next chunk, if we're not at the end
                    startTextChunk(info);
                    if (i + 1 == length) {
#if DEBUG_CHUNK_BUILDING > 1
                        fprintf(stderr, " | -> Record last chunk of inline text box!\n");
#endif

                        startTextChunk(info);
                        SVGInlineBoxCharacterRange &range = info.chunk.boxes.last();

                        info.assignChunkProperties = false;
                        info.chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle());
                        info.chunk.isTextPath = info.handlingTextPath;
                        info.chunk.anchor = text->style()->svgStyle()->textAnchor();
                        info.chunk.textLength = textContent->textLength().value();
                        info.chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust();

                        range.box = curr;
                        range.startOffset = i;

                        ASSERT(!range.isOpen());
                        ASSERT(!range.isClosed());
                    }
                }

                // This should only hold true for the first character of the first chunk
                if (isFirstCharacter) {
                    isFirstCharacter = false;
                }

                ++info.it;
            }

#if DEBUG_CHUNK_BUILDING > 1
            fprintf(stderr, " -> Finished inline text box!\n");
#endif

            SVGInlineBoxCharacterRange &range = info.chunk.boxes.last();
            if (!range.isOpen() && !range.isClosed()) {
#if DEBUG_CHUNK_BUILDING > 1
                fprintf(stderr, " -> Last range not closed - closing with endOffset: %i\n", length);
#endif

                // Current text chunk is not yet closed. Finish the current range, but don't start a new chunk.
                range.endOffset = length;

                if (info.it != svgChars.end()) {
#if DEBUG_CHUNK_BUILDING > 1
                    fprintf(stderr, " -> Not at last character yet!\n");
#endif

                    // If we're not at the end of the last box to be processed, and if the next
                    // character starts a new chunk, then close the current chunk and start a new one.
                    if ((*info.it).newTextChunk) {
#if DEBUG_CHUNK_BUILDING > 1
                        fprintf(stderr, " -> Next character starts new chunk! Closing current chunk, and starting a new one...\n");
#endif

                        closeTextChunk(info);
                        startTextChunk(info);
                    } else {
                        // Just start a new character range
                        info.chunk.boxes.append(SVGInlineBoxCharacterRange());

#if DEBUG_CHUNK_BUILDING > 1
                        fprintf(stderr, " -> Next character does NOT start a new chunk! Starting new character range...\n");
#endif
                    }
                } else {
#if DEBUG_CHUNK_BUILDING > 1
                    fprintf(stderr, " -> Closing final chunk! Finished processing!\n");
#endif

                    // Close final chunk, once we're at the end of the last box
                    closeTextChunk(info);
                }
            }
        } else {
            ASSERT(curr->isInlineFlowBox());
            InlineFlowBox *flowBox = static_cast<InlineFlowBox *>(curr);

            bool isTextPath = flowBox->object()->element()->hasTagName(SVGNames::textPathTag);

#if DEBUG_CHUNK_BUILDING > 1
            fprintf(stderr, " -> Handle inline flow box (%p), isTextPath=%i\n", flowBox, (int) isTextPath);
#endif

            if (isTextPath) {
                info.handlingTextPath = true;
            }

            buildTextChunks(svgChars, flowBox, info);

            if (isTextPath) {
                info.handlingTextPath = false;
            }
        }
    }

#if DEBUG_CHUNK_BUILDING > 1
    fprintf(stderr, " <- buildTextChunks(start=%p)\n", start);
#endif
}

const Vector<SVGTextChunk> &SVGRootInlineBox::svgTextChunks() const
{
    return m_svgTextChunks;
}

void SVGRootInlineBox::layoutTextChunks()
{
    Vector<SVGTextChunk>::iterator it = m_svgTextChunks.begin();
    Vector<SVGTextChunk>::iterator end = m_svgTextChunks.end();

    for (; it != end; ++it) {
        SVGTextChunk &chunk = *it;

#if DEBUG_CHUNK_BUILDING > 0
        {
            fprintf(stderr, "Handle TEXT CHUNK! anchor=%i, textLength=%f, lengthAdjust=%i, isVerticalText=%i, isTextPath=%i start=%p, end=%p -> dist: %i\n",
                    (int) chunk.anchor, chunk.textLength, (int) chunk.lengthAdjust, (int) chunk.isVerticalText,
                    (int) chunk.isTextPath, chunk.start, chunk.end, (unsigned int)(chunk.end - chunk.start));

            Vector<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin();
            Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = chunk.boxes.end();

            unsigned int i = 0;
            for (; boxIt != boxEnd; ++boxIt) {
                SVGInlineBoxCharacterRange &range = *boxIt; i++;
                fprintf(stderr, " -> RANGE %i STARTOFFSET: %i, ENDOFFSET: %i, BOX: %p\n", i, range.startOffset, range.endOffset, range.box);
            }
        }
#endif

        if (chunk.isTextPath) {
            continue;
        }

        // text-path & textLength, with lengthAdjust="spacing" is already handled for textPath layouts.
        applyTextLengthCorrectionToTextChunk(chunk);

        // text-anchor is already handled for textPath layouts.
        applyTextAnchorToTextChunk(chunk);
    }
}

static inline void addPaintServerToTextDecorationInfo(ETextDecoration decoration, SVGTextDecorationInfo &info, RenderObject *object)
{
    if (object->style()->svgStyle()->hasFill()) {
        info.fillServerMap.set(decoration, object);
    }

    if (object->style()->svgStyle()->hasStroke()) {
        info.strokeServerMap.set(decoration, object);
    }
}

SVGTextDecorationInfo SVGRootInlineBox::retrievePaintServersForTextDecoration(RenderObject *start)
{
    ASSERT(start);

    Vector<RenderObject *> parentChain;
    while ((start = start->parent())) {
        parentChain.prepend(start);

        // Stop at our direct <text> parent.
        if (start->isSVGText()) {
            break;
        }
    }

    Vector<RenderObject *>::iterator it = parentChain.begin();
    Vector<RenderObject *>::iterator end = parentChain.end();

    SVGTextDecorationInfo info;

    for (; it != end; ++it) {
        RenderObject *object = *it;
        ASSERT(object);

        RenderStyle *style = object->style();
        ASSERT(style);

        int decorations = style->textDecoration();
        if (decorations != NONE) {
            if (decorations & OVERLINE) {
                addPaintServerToTextDecorationInfo(OVERLINE, info, object);
            }

            if (decorations & UNDERLINE) {
                addPaintServerToTextDecorationInfo(UNDERLINE, info, object);
            }

            if (decorations & LINE_THROUGH) {
                addPaintServerToTextDecorationInfo(LINE_THROUGH, info, object);
            }
        }
    }

    return info;
}

void SVGRootInlineBox::walkTextChunks(SVGTextChunkWalkerBase *walker, const SVGInlineTextBox *textBox)
{
    ASSERT(walker);

    Vector<SVGTextChunk>::iterator it = m_svgTextChunks.begin();
    Vector<SVGTextChunk>::iterator itEnd = m_svgTextChunks.end();

    for (; it != itEnd; ++it) {
        SVGTextChunk &curChunk = *it;

        Vector<SVGInlineBoxCharacterRange>::iterator boxIt = curChunk.boxes.begin();
        Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = curChunk.boxes.end();

        InlineBox *lastNotifiedBox = nullptr;
        InlineBox *prevBox = nullptr;

        unsigned int chunkOffset = 0;
        bool startedFirstChunk = false;

        for (; boxIt != boxEnd; ++boxIt) {
            SVGInlineBoxCharacterRange &range = *boxIt;

            ASSERT(range.box->isInlineTextBox());
            SVGInlineTextBox *rangeTextBox = static_cast<SVGInlineTextBox *>(range.box);

            if (textBox && rangeTextBox != textBox) {
                chunkOffset += range.endOffset - range.startOffset;
                continue;
            }

            // Eventually notify that we started a new chunk
            if (!textBox && !startedFirstChunk) {
                startedFirstChunk = true;

                lastNotifiedBox = range.box;
                walker->start(range.box);
            } else {
                // Eventually apply new style, as this chunk spans multiple boxes (with possible different styling)
                if (prevBox && prevBox != range.box) {
                    lastNotifiedBox = range.box;

                    walker->end(prevBox);
                    walker->start(lastNotifiedBox);
                }
            }

            unsigned int length = range.endOffset - range.startOffset;

            Vector<SVGChar>::iterator itCharBegin = curChunk.start + chunkOffset;
            Vector<SVGChar>::iterator itCharEnd = curChunk.start + chunkOffset + length;
            ASSERT(itCharEnd <= curChunk.end);

            // Process this chunk portion
            if (textBox) {
                (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd);
            } else {
                if (walker->setupFill(range.box)) {
                    (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd);
                }

                if (walker->setupStroke(range.box)) {
                    (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd);
                }
            }

            chunkOffset += length;

            if (!textBox) {
                prevBox = range.box;
            }
        }

        if (!textBox && startedFirstChunk) {
            walker->end(lastNotifiedBox);
        }
    }
}

} // namespace WebCore

#endif // ENABLE(SVG)
