| /* |
| * 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<SVGInlineTextBox*>& 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<SVGInlineTextBox*>& 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<SVGInlineTextBox*>& 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<SVGTextContentElement::SVGLengthAdjustType>(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<SVGInlineTextBox*>& 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<SVGInlineTextBox*>& 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<SVGTextFragment>& 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<SVGTextFragment>& 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<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments(); |
| if (fragments.isEmpty()) |
| continue; |
| processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments); |
| } |
| } |
| |
| void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& 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<SVGTextFragment>& 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) |