blob: d26d868fff1e785bb41fb171fbc765be9bb9dce8 [file] [log] [blame]
/*
* 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 "core/rendering/svg/SVGTextChunkBuilder.h"
#include "core/rendering/svg/RenderSVGInlineText.h"
#include "core/rendering/svg/SVGInlineTextBox.h"
#include "core/svg/SVGLengthContext.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())) {
SVGLengthContext lengthContext(textContentElement);
desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
switch (textContentElement->lengthAdjustCurrentValue()) {
case SVGLengthAdjustUnknown:
break;
case SVGLengthAdjustSpacing:
chunkStyle |= SVGTextChunk::LengthAdjustSpacing;
break;
case SVGLengthAdjustSpacingAndGlyphs:
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);
}
}