/* * Copyright (C) 2006 Oliver Hunt * (C) 2006 Apple Computer Inc. * (C) 2007 Nikolas Zimmermann * 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(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(curr)->buildLayoutInformation(info, lastGlyph); else { ASSERT(curr->isInlineFlowBox()); InlineFlowBox* flowBox = static_cast(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(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(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 tempChunks; SVGCharacterLayoutInfo tempCharInfo; buildLayoutInformation(flowBox, tempCharInfo); SVGTextChunkLayoutInfo tempChunkInfo; tempChunkInfo.buildTextChunks(tempCharInfo.svgChars.begin(), tempCharInfo.svgChars.end(), flowBox); tempChunks = tempChunkInfo.textChunks(); Vector::iterator it = tempChunks.begin(); Vector::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(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(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 boxes; #endif // Loop through all text chunks const Vector::const_iterator end = m_svgTextChunks.end(); for (Vector::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::const_iterator boxEnd = chunk.boxes.end(); for (Vector::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(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::iterator itCharBegin = chunk.start + processedChunkCharacters; Vector::iterator itCharEnd = chunk.start + processedChunkCharacters + rangeLength; ASSERT(itCharEnd <= chunk.end); // Loop through all characters in range int processedRangeCharacters = 0; for (Vector::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::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::const_iterator it = boxes.begin(); const ListHashSet::const_iterator end = boxes.end(); for (; it != end; ++it) { const SVGInlineTextBox* box = *it; const Vector& parts = box->svgTextChunkParts(); fprintf(stderr, " Box %p contains %i text chunk parts:\n", box, static_cast(parts.size())); Vector::const_iterator partIt = parts.begin(); const Vector::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)