/* * 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" #if ENABLE(SVG) #include "SVGTextChunkBuilder.h" #include "RenderSVGInlineText.h" #include "SVGElement.h" #include "SVGInlineTextBox.h" namespace WebCore { SVGTextChunkBuilder::SVGTextChunkBuilder() { } void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const { DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ()); if (!m_textBoxTransformations.contains(textBox)) { transform = s_identityTransform; return; } transform = m_textBoxTransformations.get(textBox); } void SVGTextChunkBuilder::buildTextChunks(Vector& lineLayoutBoxes) { if (lineLayoutBoxes.isEmpty()) return; bool foundStart = false; unsigned lastChunkStartPosition = 0; unsigned boxPosition = 0; unsigned boxCount = lineLayoutBoxes.size(); for (; boxPosition < boxCount; ++boxPosition) { SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition]; if (!textBox->startsNewTextChunk()) continue; if (!foundStart) { lastChunkStartPosition = boxPosition; foundStart = true; } else { ASSERT(boxPosition > lastChunkStartPosition); addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition); lastChunkStartPosition = boxPosition; } } if (!foundStart) return; if (boxPosition - lastChunkStartPosition > 0) addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition); } void SVGTextChunkBuilder::layoutTextChunks(Vector& lineLayoutBoxes) { buildTextChunks(lineLayoutBoxes); if (m_textChunks.isEmpty()) return; unsigned chunkCount = m_textChunks.size(); for (unsigned i = 0; i < chunkCount; ++i) processTextChunk(m_textChunks[i]); m_textChunks.clear(); } void SVGTextChunkBuilder::addTextChunk(Vector& lineLayoutBoxes, unsigned boxStart, unsigned boxCount) { SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart]; ASSERT(textBox); RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer()); ASSERT(textRenderer); const RenderStyle* style = textRenderer->style(); ASSERT(style); const SVGRenderStyle* svgStyle = style->svgStyle(); ASSERT(svgStyle); // Build chunk style flags. unsigned chunkStyle = SVGTextChunk::DefaultStyle; // Handle 'direction' property. if (!style->isLeftToRightDirection()) chunkStyle |= SVGTextChunk::RightToLeftText; // Handle 'writing-mode' property. if (svgStyle->isVerticalWritingMode()) chunkStyle |= SVGTextChunk::VerticalText; // Handle 'text-anchor' property. switch (svgStyle->textAnchor()) { case TA_START: break; case TA_MIDDLE: chunkStyle |= SVGTextChunk::MiddleAnchor; break; case TA_END: chunkStyle |= SVGTextChunk::EndAnchor; break; }; // Handle 'lengthAdjust' property. float desiredTextLength = 0; if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer->parent())) { desiredTextLength = textContentElement->specifiedTextLength().value(textContentElement); switch (static_cast(textContentElement->lengthAdjust())) { case SVGTextContentElement::LENGTHADJUST_UNKNOWN: break; case SVGTextContentElement::LENGTHADJUST_SPACING: chunkStyle |= SVGTextChunk::LengthAdjustSpacing; break; case SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS: chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs; break; }; } SVGTextChunk chunk(chunkStyle, desiredTextLength); Vector& boxes = chunk.boxes(); for (unsigned i = boxStart; i < boxStart + boxCount; ++i) boxes.append(lineLayoutBoxes[i]); m_textChunks.append(chunk); } void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk) { bool processTextLength = chunk.hasDesiredTextLength(); bool processTextAnchor = chunk.hasTextAnchor(); if (!processTextAnchor && !processTextLength) return; const Vector& boxes = chunk.boxes(); unsigned boxCount = boxes.size(); if (!boxCount) return; // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes). float chunkLength = 0; unsigned chunkCharacters = 0; chunk.calculateLength(chunkLength, chunkCharacters); bool isVerticalText = chunk.isVerticalText(); if (processTextLength) { if (chunk.hasLengthAdjustSpacing()) { float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters; unsigned atCharacter = 0; for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { Vector& fragments = boxes[boxPosition]->textFragments(); if (fragments.isEmpty()) continue; processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter); } } else { ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs()); float textLengthScale = chunk.desiredTextLength() / chunkLength; AffineTransform spacingAndGlyphsTransform; bool foundFirstFragment = false; for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { SVGInlineTextBox* textBox = boxes[boxPosition]; Vector& fragments = textBox->textFragments(); if (fragments.isEmpty()) continue; if (!foundFirstFragment) { foundFirstFragment = true; buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform); } m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform); } } } if (!processTextAnchor) return; // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift. if (processTextLength && chunk.hasLengthAdjustSpacing()) { chunkLength = 0; chunkCharacters = 0; chunk.calculateLength(chunkLength, chunkCharacters); } float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength); for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { Vector& fragments = boxes[boxPosition]->textFragments(); if (fragments.isEmpty()) continue; processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments); } } void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector& fragments, unsigned& atCharacter) { unsigned fragmentCount = fragments.size(); for (unsigned i = 0; i < fragmentCount; ++i) { SVGTextFragment& fragment = fragments[i]; if (isVerticalText) fragment.y += textLengthShift * atCharacter; else fragment.x += textLengthShift * atCharacter; atCharacter += fragment.length; } } void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector& fragments) { unsigned fragmentCount = fragments.size(); for (unsigned i = 0; i < fragmentCount; ++i) { SVGTextFragment& fragment = fragments[i]; if (isVerticalText) fragment.y += textAnchorShift; else fragment.x += textAnchorShift; } } void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform) { spacingAndGlyphsTransform.translate(fragment.x, fragment.y); if (isVerticalText) spacingAndGlyphsTransform.scaleNonUniform(1, scale); else spacingAndGlyphsTransform.scaleNonUniform(scale, 1); spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y); } } #endif // ENABLE(SVG)