| /* |
| * 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 |