WebCore/rendering/SVGRootInlineBox.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) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
 *           (C) 2006 Apple Computer Inc.
 *           (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "SVGRootInlineBox.h"

#if ENABLE(SVG)
#include "GraphicsContext.h"
#include "RenderBlock.h"
#include "SVGInlineFlowBox.h"
#include "SVGInlineTextBox.h"
#include "SVGRenderSupport.h"
#include "SVGTextLayoutUtilities.h"
#include "SVGTextPositioningElement.h"

// Text chunk part propagation can be traced by setting this variable > 0.
#define DEBUG_CHUNK_PART_PROPAGATION 0

namespace WebCore {

void SVGRootInlineBox::paint(PaintInfo& paintInfo, int, int)
{
    ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection);
    ASSERT(!paintInfo.context->paintingDisabled());

    RenderObject* boxRenderer = renderer();
    ASSERT(boxRenderer);

    PaintInfo childPaintInfo(paintInfo);
    childPaintInfo.context->save();

    if (SVGRenderSupport::prepareToRenderSVGContent(boxRenderer, childPaintInfo)) {
        for (InlineBox* child = firstChild(); child; child = child->nextOnLine())
            child->paint(childPaintInfo, 0, 0);
    }

    SVGRenderSupport::finishRenderSVGContent(boxRenderer, childPaintInfo, paintInfo.context);
    childPaintInfo.context->restore();
}

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

    // Build layout information for all contained render objects
    SVGCharacterLayoutInfo charInfo;
    buildLayoutInformation(this, charInfo);
    m_svgChars = charInfo.svgChars;

    // 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.
    SVGTextChunkLayoutInfo chunkInfo;
    chunkInfo.buildTextChunks(m_svgChars.begin(), m_svgChars.end(), this);

    // Layout all text chunks
    // text-anchor needs to be applied to individual chunks.
    chunkInfo.layoutTextChunks();
    m_svgTextChunks = chunkInfo.textChunks();

    // Propagate text chunk part information to all SVGInlineTextBoxes, see SVGTextChunkLayoutInfo.h for details
    propagateTextChunkPartInformation();

    // Layout all child boxes.
    layoutChildBoxes(this);

    // Resize our root box and our RenderSVGText parent block
    layoutRootBox();
}

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

        SVGTextPositioningElement* positioningElement = static_cast<SVGTextPositioningElement*>(start->renderer()->node());
        ASSERT(positioningElement);
        ASSERT(positioningElement->parentNode());

        info.addLayoutInformation(positioningElement);
    }

    SVGLastGlyphInfo lastGlyph;
    
    for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) {
        if (curr->renderer()->isText())
            static_cast<SVGInlineTextBox*>(curr)->buildLayoutInformation(info, lastGlyph);
        else {
            ASSERT(curr->isInlineFlowBox());
            InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(curr);

            // Skip generated content.
            if (!flowBox->renderer()->node())
                continue;

            bool isAnchor = flowBox->renderer()->node()->hasTagName(SVGNames::aTag);
            bool isTextPath = flowBox->renderer()->node()->hasTagName(SVGNames::textPathTag);

            if (!isTextPath && !isAnchor) {
                SVGTextPositioningElement* positioningElement = static_cast<SVGTextPositioningElement*>(flowBox->renderer()->node());
                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 = 0;
                Node* node = flowBox->renderer()->node();
                if (node && node->isSVGElement())
                    textContent = static_cast<SVGTextContentElement*>(node);
                ASSERT(textContent);

                ELengthAdjust lengthAdjust = (ELengthAdjust) textContent->lengthAdjust();
                ETextAnchor anchor = flowBox->renderer()->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<SVGTextChunk> tempChunks;

                SVGCharacterLayoutInfo tempCharInfo;
                buildLayoutInformation(flowBox, tempCharInfo);

                SVGTextChunkLayoutInfo tempChunkInfo;
                tempChunkInfo.buildTextChunks(tempCharInfo.svgChars.begin(), tempCharInfo.svgChars.end(), flowBox);
                tempChunks = tempChunkInfo.textChunks();

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

                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::layoutChildBoxes(InlineFlowBox* start)
{
    for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) {
        if (child->renderer()->isText()) {
            SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(child);
            IntRect boxRect = textBox->calculateBoundaries();
            textBox->setX(boxRect.x());
            textBox->setY(boxRect.y());
            textBox->setWidth(boxRect.width());
            textBox->setHeight(boxRect.height());
        } else {
            ASSERT(child->isInlineFlowBox());
   
            // Skip generated content.
            if (!child->renderer()->node())
                continue;

            SVGInlineFlowBox* flowBox = static_cast<SVGInlineFlowBox*>(child);
            layoutChildBoxes(flowBox);

            IntRect boxRect = flowBox->calculateBoundaries();
            flowBox->setX(boxRect.x());
            flowBox->setY(boxRect.y());
            flowBox->setWidth(boxRect.width());
            flowBox->setHeight(boxRect.height());
        }
    }
}

void SVGRootInlineBox::layoutRootBox()
{
    RenderBlock* parentBlock = block();
    ASSERT(parentBlock);

    IntRect childRect;
    for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) {
        // Skip generated content.
        if (!child->renderer()->node())
            continue;
        childRect.unite(child->calculateBoundaries());
    }

    int xBlock = childRect.x();
    int yBlock = childRect.y();
    int widthBlock = childRect.width();
    int heightBlock = childRect.height();

    // Finally, assign the root block position, now that all content is laid out.
    parentBlock->setLocation(xBlock, yBlock);
    parentBlock->setWidth(widthBlock);
    parentBlock->setHeight(heightBlock);

    // Position all children relative to the parent block.
    for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) {
        // Skip generated content.
        if (!child->renderer()->node())
            continue;
        child->adjustPosition(-xBlock, -yBlock);
    }

    // Position ourselves.
    setX(0);
    setY(0);
    setWidth(widthBlock);
    setHeight(heightBlock);
    setBlockHeight(heightBlock);
    setLineTopBottomPositions(0, heightBlock);
}

void SVGRootInlineBox::propagateTextChunkPartInformation()
{
#if DEBUG_CHUNK_PART_PROPAGATION > 0
    ListHashSet<SVGInlineTextBox*> boxes;
#endif

    // Loop through all text chunks
    const Vector<SVGTextChunk>::const_iterator end = m_svgTextChunks.end();
    for (Vector<SVGTextChunk>::const_iterator it = m_svgTextChunks.begin(); it != end; ++it) {
        const SVGTextChunk& chunk = *it;
        int processedChunkCharacters = 0;

        // Loop through all ranges contained in this chunk
        const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = chunk.boxes.end();
        for (Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = chunk.boxes.begin(); boxIt != boxEnd; ++boxIt) {
            const SVGInlineBoxCharacterRange& range = *boxIt;
            ASSERT(range.box->isSVGInlineTextBox());

            // Access style & font information of this text box
            SVGInlineTextBox* rangeTextBox = static_cast<SVGInlineTextBox*>(range.box);
            rangeTextBox->setChunkTransformation(chunk.ctm);

            RenderText* text = rangeTextBox->textRenderer();
            ASSERT(text);

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

            const Font& font = style->font();
 
            // Figure out first and last character of this range in this chunk
            int rangeLength = range.endOffset - range.startOffset;
            Vector<SVGChar>::iterator itCharBegin = chunk.start + processedChunkCharacters;
            Vector<SVGChar>::iterator itCharEnd = chunk.start + processedChunkCharacters + rangeLength;
            ASSERT(itCharEnd <= chunk.end);

            // Loop through all characters in range 
            int processedRangeCharacters = 0;
            for (Vector<SVGChar>::iterator itChar = itCharBegin; itChar != itCharEnd; ++itChar) {
                if (itChar->isHidden()) {
                    ++processedRangeCharacters;
                    continue;
                }

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

                    ++itSearch;
                }

                // Calculate text chunk part information for this chunk sub-range
                const UChar* partStart = text->characters() + rangeTextBox->start() + range.startOffset + processedRangeCharacters;

                SVGTextChunkPart part;
                part.firstCharacter = itChar;
                part.length = itSearch - itChar;
                part.width = font.floatWidth(svgTextRunForInlineTextBox(partStart, part.length, style, rangeTextBox));
                part.height = font.height();
                part.offset = range.startOffset + processedRangeCharacters;
                rangeTextBox->addChunkPartInformation(part);
                processedRangeCharacters += part.length;

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

            ASSERT(processedRangeCharacters == rangeLength);
            processedChunkCharacters += rangeLength;

#if DEBUG_CHUNK_PART_PROPAGATION > 0
            boxes.add(rangeTextBox);
#endif
        }
    }

#if DEBUG_CHUNK_PART_PROPAGATION > 0
    {
        fprintf(stderr, "Propagated text chunk part information:\n");

        ListHashSet<SVGInlineTextBox*>::const_iterator it = boxes.begin();
        const ListHashSet<SVGInlineTextBox*>::const_iterator end = boxes.end();

        for (; it != end; ++it) {
            const SVGInlineTextBox* box = *it;
            const Vector<SVGTextChunkPart>& parts = box->svgTextChunkParts();

            fprintf(stderr, " Box %p contains %i text chunk parts:\n", box, static_cast<int>(parts.size()));
            Vector<SVGTextChunkPart>::const_iterator partIt = parts.begin();
            const Vector<SVGTextChunkPart>::const_iterator partEnd = parts.end();
            for (; partIt != partEnd; ++partIt) {
                const SVGTextChunkPart& part = *partIt;
                fprintf(stderr, "   -> firstCharacter x=%lf, y=%lf, offset=%i, length=%i, width=%lf, height=%lf, textRenderer=%p\n"
                              , part.firstCharacter->x, part.firstCharacter->y, part.offset, part.length, part.width, part.height, box->textRenderer());
            }
        }
    }
#endif
}

} // namespace WebCore

#endif // ENABLE(SVG)