blob: a63eb80a779d7768da4772bb0d6afe19e3521bbd [file] [log] [blame]
/*
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
* Copyright (C) 2015 Apple Inc. 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 "SVGTextChunk.h"
#include "RenderSVGInlineText.h"
#include "SVGInlineTextBoxInlines.h"
#include "SVGTextContentElement.h"
#include "SVGTextFragment.h"
namespace WebCore {
SVGTextChunk::SVGTextChunk(const Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned first, unsigned limit)
{
ASSERT(first < limit);
ASSERT(limit <= lineLayoutBoxes.size());
const SVGInlineTextBox* box = lineLayoutBoxes[first];
const RenderStyle& style = box->renderer().style();
const SVGRenderStyle& svgStyle = style.svgStyle();
if (!style.isLeftToRightDirection())
m_chunkStyle |= SVGTextChunk::RightToLeftText;
if (style.isVerticalWritingMode())
m_chunkStyle |= SVGTextChunk::VerticalText;
switch (svgStyle.textAnchor()) {
case TextAnchor::Start:
break;
case TextAnchor::Middle:
m_chunkStyle |= MiddleAnchor;
break;
case TextAnchor::End:
m_chunkStyle |= EndAnchor;
break;
}
if (auto* textContentElement = SVGTextContentElement::elementFromRenderer(box->renderer().parent())) {
SVGLengthContext lengthContext(textContentElement);
m_desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
switch (textContentElement->lengthAdjust()) {
case SVGLengthAdjustUnknown:
break;
case SVGLengthAdjustSpacing:
m_chunkStyle |= LengthAdjustSpacing;
break;
case SVGLengthAdjustSpacingAndGlyphs:
m_chunkStyle |= LengthAdjustSpacingAndGlyphs;
break;
}
}
for (unsigned i = first; i < limit; ++i)
m_boxes.append(lineLayoutBoxes[i]);
}
unsigned SVGTextChunk::totalCharacters() const
{
unsigned characters = 0;
for (auto* box : m_boxes) {
for (auto& fragment : box->textFragments())
characters += fragment.length;
}
return characters;
}
float SVGTextChunk::totalLength() const
{
const SVGTextFragment* firstFragment = nullptr;
const SVGTextFragment* lastFragment = nullptr;
for (auto* box : m_boxes) {
auto& fragments = box->textFragments();
if (fragments.size()) {
firstFragment = &(*fragments.begin());
break;
}
}
for (auto it = m_boxes.rbegin(), end = m_boxes.rend(); it != end; ++it) {
auto& fragments = (*it)->textFragments();
if (fragments.size()) {
lastFragment = &(*fragments.rbegin());
break;
}
}
ASSERT(!firstFragment == !lastFragment);
if (!firstFragment)
return 0;
if (m_chunkStyle & VerticalText)
return (lastFragment->y + lastFragment->height) - firstFragment->y;
return (lastFragment->x + lastFragment->width) - firstFragment->x;
}
float SVGTextChunk::totalAnchorShift() const
{
float length = totalLength();
if (m_chunkStyle & MiddleAnchor)
return -length / 2;
if (m_chunkStyle & EndAnchor)
return m_chunkStyle & RightToLeftText ? 0 : -length;
return m_chunkStyle & RightToLeftText ? -length : 0;
}
void SVGTextChunk::layout(HashMap<SVGInlineTextBox*, AffineTransform>& textBoxTransformations) const
{
if (hasDesiredTextLength()) {
if (hasLengthAdjustSpacing())
processTextLengthSpacingCorrection();
else {
ASSERT(hasLengthAdjustSpacingAndGlyphs());
buildBoxTransformations(textBoxTransformations);
}
}
if (hasTextAnchor())
processTextAnchorCorrection();
}
void SVGTextChunk::processTextLengthSpacingCorrection() const
{
float textLengthShift = (desiredTextLength() - totalLength()) / totalCharacters();
bool isVerticalText = m_chunkStyle & VerticalText;
unsigned atCharacter = 0;
for (auto* box : m_boxes) {
for (auto& fragment : box->textFragments()) {
if (isVerticalText)
fragment.y += textLengthShift * atCharacter;
else
fragment.x += textLengthShift * atCharacter;
atCharacter += fragment.length;
}
}
}
void SVGTextChunk::buildBoxTransformations(HashMap<SVGInlineTextBox*, AffineTransform>& textBoxTransformations) const
{
AffineTransform spacingAndGlyphsTransform;
bool foundFirstFragment = false;
for (auto* box : m_boxes) {
if (!foundFirstFragment) {
if (!boxSpacingAndGlyphsTransform(box, spacingAndGlyphsTransform))
continue;
foundFirstFragment = true;
}
textBoxTransformations.set(box, spacingAndGlyphsTransform);
}
}
bool SVGTextChunk::boxSpacingAndGlyphsTransform(const SVGInlineTextBox* box, AffineTransform& spacingAndGlyphsTransform) const
{
auto& fragments = box->textFragments();
if (fragments.isEmpty())
return false;
const SVGTextFragment& fragment = fragments.first();
float scale = desiredTextLength() / totalLength();
spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
if (m_chunkStyle & VerticalText)
spacingAndGlyphsTransform.scaleNonUniform(1, scale);
else
spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
return true;
}
void SVGTextChunk::processTextAnchorCorrection() const
{
float textAnchorShift = totalAnchorShift();
bool isVerticalText = m_chunkStyle & VerticalText;
for (auto* box : m_boxes) {
for (auto& fragment : box->textFragments()) {
if (isVerticalText)
fragment.y += textAnchorShift;
else
fragment.x += textAnchorShift;
}
}
}
} // namespace WebCore