1/*
2 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
3 * Copyright (C) 2015 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22#include "SVGTextChunk.h"
23
24#include "SVGInlineTextBox.h"
25#include "SVGTextContentElement.h"
26#include "SVGTextFragment.h"
27
28namespace WebCore {
29
30SVGTextChunk::SVGTextChunk(const Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned first, unsigned limit)
31{
32 ASSERT(first < limit);
33 ASSERT(limit <= lineLayoutBoxes.size());
34
35 const SVGInlineTextBox* box = lineLayoutBoxes[first];
36 const RenderStyle& style = box->renderer().style();
37 const SVGRenderStyle& svgStyle = style.svgStyle();
38
39 if (!style.isLeftToRightDirection())
40 m_chunkStyle |= SVGTextChunk::RightToLeftText;
41
42 if (style.isVerticalWritingMode())
43 m_chunkStyle |= SVGTextChunk::VerticalText;
44
45 switch (svgStyle.textAnchor()) {
46 case TextAnchor::Start:
47 break;
48 case TextAnchor::Middle:
49 m_chunkStyle |= MiddleAnchor;
50 break;
51 case TextAnchor::End:
52 m_chunkStyle |= EndAnchor;
53 break;
54 }
55
56 if (auto* textContentElement = SVGTextContentElement::elementFromRenderer(box->renderer().parent())) {
57 SVGLengthContext lengthContext(textContentElement);
58 m_desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
59
60 switch (textContentElement->lengthAdjust()) {
61 case SVGLengthAdjustUnknown:
62 break;
63 case SVGLengthAdjustSpacing:
64 m_chunkStyle |= LengthAdjustSpacing;
65 break;
66 case SVGLengthAdjustSpacingAndGlyphs:
67 m_chunkStyle |= LengthAdjustSpacingAndGlyphs;
68 break;
69 }
70 }
71
72 for (unsigned i = first; i < limit; ++i)
73 m_boxes.append(lineLayoutBoxes[i]);
74}
75
76unsigned SVGTextChunk::totalCharacters() const
77{
78 unsigned characters = 0;
79 for (auto* box : m_boxes) {
80 for (auto& fragment : box->textFragments())
81 characters += fragment.length;
82 }
83 return characters;
84}
85
86float SVGTextChunk::totalLength() const
87{
88 const SVGTextFragment* firstFragment = nullptr;
89 const SVGTextFragment* lastFragment = nullptr;
90
91 for (auto* box : m_boxes) {
92 auto& fragments = box->textFragments();
93 if (fragments.size()) {
94 firstFragment = &(*fragments.begin());
95 break;
96 }
97 }
98
99 for (auto it = m_boxes.rbegin(), end = m_boxes.rend(); it != end; ++it) {
100 auto& fragments = (*it)->textFragments();
101 if (fragments.size()) {
102 lastFragment = &(*fragments.rbegin());
103 break;
104 }
105 }
106
107 ASSERT(!firstFragment == !lastFragment);
108 if (!firstFragment)
109 return 0;
110
111 if (m_chunkStyle & VerticalText)
112 return (lastFragment->y + lastFragment->height) - firstFragment->y;
113
114 return (lastFragment->x + lastFragment->width) - firstFragment->x;
115}
116
117float SVGTextChunk::totalAnchorShift() const
118{
119 float length = totalLength();
120 if (m_chunkStyle & MiddleAnchor)
121 return -length / 2;
122 if (m_chunkStyle & EndAnchor)
123 return m_chunkStyle & RightToLeftText ? 0 : -length;
124 return m_chunkStyle & RightToLeftText ? -length : 0;
125}
126
127void SVGTextChunk::layout(HashMap<SVGInlineTextBox*, AffineTransform>& textBoxTransformations) const
128{
129 if (hasDesiredTextLength()) {
130 if (hasLengthAdjustSpacing())
131 processTextLengthSpacingCorrection();
132 else {
133 ASSERT(hasLengthAdjustSpacingAndGlyphs());
134 buildBoxTransformations(textBoxTransformations);
135 }
136 }
137
138 if (hasTextAnchor())
139 processTextAnchorCorrection();
140}
141
142void SVGTextChunk::processTextLengthSpacingCorrection() const
143{
144 float textLengthShift = (desiredTextLength() - totalLength()) / totalCharacters();
145 bool isVerticalText = m_chunkStyle & VerticalText;
146 unsigned atCharacter = 0;
147
148 for (auto* box : m_boxes) {
149 for (auto& fragment : box->textFragments()) {
150 if (isVerticalText)
151 fragment.y += textLengthShift * atCharacter;
152 else
153 fragment.x += textLengthShift * atCharacter;
154
155 atCharacter += fragment.length;
156 }
157 }
158}
159
160void SVGTextChunk::buildBoxTransformations(HashMap<SVGInlineTextBox*, AffineTransform>& textBoxTransformations) const
161{
162 AffineTransform spacingAndGlyphsTransform;
163 bool foundFirstFragment = false;
164
165 for (auto* box : m_boxes) {
166 if (!foundFirstFragment) {
167 if (!boxSpacingAndGlyphsTransform(box, spacingAndGlyphsTransform))
168 continue;
169 foundFirstFragment = true;
170 }
171
172 textBoxTransformations.set(box, spacingAndGlyphsTransform);
173 }
174}
175
176bool SVGTextChunk::boxSpacingAndGlyphsTransform(const SVGInlineTextBox* box, AffineTransform& spacingAndGlyphsTransform) const
177{
178 auto& fragments = box->textFragments();
179 if (fragments.isEmpty())
180 return false;
181
182 const SVGTextFragment& fragment = fragments.first();
183 float scale = desiredTextLength() / totalLength();
184
185 spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
186
187 if (m_chunkStyle & VerticalText)
188 spacingAndGlyphsTransform.scaleNonUniform(1, scale);
189 else
190 spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
191
192 spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
193 return true;
194}
195
196void SVGTextChunk::processTextAnchorCorrection() const
197{
198 float textAnchorShift = totalAnchorShift();
199 bool isVerticalText = m_chunkStyle & VerticalText;
200
201 for (auto* box : m_boxes) {
202 for (auto& fragment : box->textFragments()) {
203 if (isVerticalText)
204 fragment.y += textAnchorShift;
205 else
206 fragment.x += textAnchorShift;
207 }
208 }
209}
210
211} // namespace WebCore
212