diff -r 000000000000 -r 4f2f89ce4247 WebCore/rendering/SVGTextChunkLayoutInfo.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/rendering/SVGTextChunkLayoutInfo.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,493 @@ +/* + * Copyright (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 "SVGTextChunkLayoutInfo.h" + +#if ENABLE(SVG) +#include "InlineFlowBox.h" +#include "SVGInlineTextBox.h" +#include "SVGRenderStyle.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 float cummulatedWidthOrHeightOfTextChunk(SVGTextChunk& chunk, bool calcWidthOnly) +{ + float length = 0.0f; + Vector::iterator charIt = chunk.start; + + Vector::iterator it = chunk.boxes.begin(); + Vector::iterator end = chunk.boxes.end(); + + for (; it != end; ++it) { + SVGInlineBoxCharacterRange& range = *it; + + SVGInlineTextBox* box = static_cast(range.box); + RenderStyle* style = box->renderer()->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::iterator itSearch = charIt + 1; + Vector::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; + + if (calcWidthOnly) + length += cummulatedWidthOfInlineBoxCharacterRange(subRange); + else + length += cummulatedHeightOfInlineBoxCharacterRange(subRange); + + // Calculate gap between the previous & current range + // ABCD - 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 charsConsumed = 0; + float glyphWidth = 0.0f; + float glyphHeight = 0.0f; + String glyphName; + String unicodeString; + box->measureCharacter(style, i + positionOffset - 1, charsConsumed, glyphName, unicodeString, glyphWidth, glyphHeight); + + if (calcWidthOnly) + length += currentCharacter.x - lastCharacter.x - glyphWidth; + else + length += currentCharacter.y - lastCharacter.y - glyphHeight; + } + + // 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); +} + +float calculateTextAnchorShiftForTextChunk(SVGTextChunk& chunk, ETextAnchor anchor) +{ + float shift = 0.0f; + + if (chunk.isVerticalText) + shift = cummulatedHeightOfTextChunk(chunk); + else + shift = cummulatedWidthOfTextChunk(chunk); + + 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::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::iterator boxIt = chunk.boxes.begin(); + Vector::iterator boxEnd = chunk.boxes.end(); + + for (; boxIt != boxEnd; ++boxIt) { + SVGInlineBoxCharacterRange& range = *boxIt; + + InlineBox* curBox = range.box; + ASSERT(curBox->isSVGInlineTextBox()); + + // Move target box + if (chunk.isVerticalText) + curBox->setY(curBox->y() + static_cast(shift)); + else + curBox->setX(curBox->x() + static_cast(shift)); + } +} + +float calculateTextLengthCorrectionForTextChunk(SVGTextChunk& chunk, ELengthAdjust lengthAdjust, float& computedLength) +{ + if (chunk.textLength <= 0.0f) + return 0.0f; + + computedLength = 0.0f; + + float computedWidth = cummulatedWidthOfTextChunk(chunk); + float computedHeight = cummulatedHeightOfTextChunk(chunk); + if ((computedWidth <= 0.0f && !chunk.isVerticalText) + || (computedHeight <= 0.0f && chunk.isVerticalText)) + return 0.0f; + + computedLength = chunk.isVerticalText ? computedHeight : computedWidth; + if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) { + if (chunk.isVerticalText) + chunk.ctm.scaleNonUniform(1.0f, chunk.textLength / computedLength); + else + chunk.ctm.scaleNonUniform(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 setting a scale factor for the whole chunk + float textLength = 0.0f; + float spacingToApply = calculateTextLengthCorrectionForTextChunk(chunk, chunk.lengthAdjust, textLength); + + 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(chunk.ctm); + newChunkCtm.translateRight(firstChar.x, firstChar.y); + newChunkCtm.translate(-firstChar.x, -firstChar.y); + + chunk.ctm = newChunkCtm; + } + + // Apply correction to chunk + if (spacingToApply != 0.0f) { + Vector::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 SVGTextChunkLayoutInfo::startTextChunk() +{ + m_chunk.boxes.clear(); + m_chunk.boxes.append(SVGInlineBoxCharacterRange()); + + m_chunk.start = m_charsIt; + m_assignChunkProperties = true; +} + +void SVGTextChunkLayoutInfo::closeTextChunk() +{ + ASSERT(!m_chunk.boxes.last().isOpen()); + ASSERT(m_chunk.boxes.last().isClosed()); + + m_chunk.end = m_charsIt; + ASSERT(m_chunk.end >= m_chunk.start); + + m_svgTextChunks.append(m_chunk); +} + +void SVGTextChunkLayoutInfo::buildTextChunks(Vector::iterator begin, Vector::iterator end, InlineFlowBox* start) +{ + m_charsBegin = begin; + m_charsEnd = end; + + m_charsIt = begin; + m_chunk = SVGTextChunk(begin); + + recursiveBuildTextChunks(start); + ASSERT(m_charsIt == m_charsEnd); +} + +void SVGTextChunkLayoutInfo::recursiveBuildTextChunks(InlineFlowBox* start) +{ +#if DEBUG_CHUNK_BUILDING > 1 + fprintf(stderr, " -> buildTextChunks(start=%p)\n", start); +#endif + + for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->renderer()->isText()) { + InlineTextBox* textBox = static_cast(curr); + + unsigned length = textBox->len(); + ASSERT(length > 0); + +#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) m_handlingTextPath); +#endif + + RenderText* text = textBox->textRenderer(); + ASSERT(text); + ASSERT(text->node()); + + SVGTextContentElement* textContent = 0; + Node* node = text->node()->parent(); + while (node && node->isSVGElement() && !textContent) { + if (static_cast(node)->isTextContent()) + textContent = static_cast(node); + else + node = node->parentNode(); + } + ASSERT(textContent); + + // Start new character range for the first chunk + bool isFirstCharacter = m_svgTextChunks.isEmpty() && m_chunk.start == m_charsIt && m_chunk.start == m_chunk.end; + if (isFirstCharacter) { + ASSERT(m_chunk.boxes.isEmpty()); + m_chunk.boxes.append(SVGInlineBoxCharacterRange()); + } else + ASSERT(!m_chunk.boxes.isEmpty()); + + // Walk string to find out new chunk positions, if existent + for (unsigned i = 0; i < length; ++i) { + ASSERT(m_charsIt != m_charsEnd); + + SVGInlineBoxCharacterRange& range = m_chunk.boxes.last(); + if (range.isOpen()) { + range.box = curr; + range.startOffset = !i ? 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 (m_assignChunkProperties) { + m_assignChunkProperties = false; + + m_chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle()); + m_chunk.isTextPath = m_handlingTextPath; + m_chunk.anchor = text->style()->svgStyle()->textAnchor(); + m_chunk.textLength = textContent->textLength().value(textContent); + m_chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); + +#if DEBUG_CHUNK_BUILDING > 1 + fprintf(stderr, " | -> Assign chunk properties, isVerticalText=%i, anchor=%i\n", m_chunk.isVerticalText, m_chunk.anchor); +#endif + } + + if (i > 0 && !isFirstCharacter && m_charsIt->newTextChunk) { + // Close mid chunk & character range + ASSERT(!range.isOpen()); + ASSERT(!range.isClosed()); + + range.endOffset = i; + closeTextChunk(); + +#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(); + if (i + 1 == length) { +#if DEBUG_CHUNK_BUILDING > 1 + fprintf(stderr, " | -> Record last chunk of inline text box!\n"); +#endif + + startTextChunk(); + SVGInlineBoxCharacterRange& range = m_chunk.boxes.last(); + + m_assignChunkProperties = false; + m_chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle()); + m_chunk.isTextPath = m_handlingTextPath; + m_chunk.anchor = text->style()->svgStyle()->textAnchor(); + m_chunk.textLength = textContent->textLength().value(textContent); + m_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; + + ++m_charsIt; + } + +#if DEBUG_CHUNK_BUILDING > 1 + fprintf(stderr, " -> Finished inline text box!\n"); +#endif + + SVGInlineBoxCharacterRange& range = m_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 (m_charsIt != m_charsEnd) { +#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 (m_charsIt->newTextChunk) { +#if DEBUG_CHUNK_BUILDING > 1 + fprintf(stderr, " -> Next character starts new chunk! Closing current chunk, and starting a new one...\n"); +#endif + + closeTextChunk(); + startTextChunk(); + } else { + // Just start a new character range + m_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(); + } + } + } else { + ASSERT(curr->isInlineFlowBox()); + InlineFlowBox* flowBox = static_cast(curr); + + // Skip generated content. + if (!flowBox->renderer()->node()) + continue; + + bool isTextPath = flowBox->renderer()->node()->hasTagName(SVGNames::textPathTag); + +#if DEBUG_CHUNK_BUILDING > 1 + fprintf(stderr, " -> Handle inline flow box (%p), isTextPath=%i\n", flowBox, (int) isTextPath); +#endif + + if (isTextPath) + m_handlingTextPath = true; + + recursiveBuildTextChunks(flowBox); + + if (isTextPath) + m_handlingTextPath = false; + } + } + +#if DEBUG_CHUNK_BUILDING > 1 + fprintf(stderr, " <- buildTextChunks(start=%p)\n", start); +#endif +} + +void SVGTextChunkLayoutInfo::layoutTextChunks() +{ + Vector::iterator it = m_svgTextChunks.begin(); + Vector::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::iterator boxIt = chunk.boxes.begin(); + Vector::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); + } +} + +} // namespace WebCore + +#endif // ENABLE(SVG)