1 /*
2 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #include "config.h"
21
22 #include "core/rendering/svg/SVGTextChunkBuilder.h"
23
24 #include "core/rendering/svg/RenderSVGInlineText.h"
25 #include "core/rendering/svg/SVGInlineTextBox.h"
26 #include "core/svg/SVGLengthContext.h"
27
28 namespace WebCore {
29
SVGTextChunkBuilder()30 SVGTextChunkBuilder::SVGTextChunkBuilder()
31 {
32 }
33
transformationForTextBox(SVGInlineTextBox * textBox,AffineTransform & transform) const34 void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const
35 {
36 DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ());
37 if (!m_textBoxTransformations.contains(textBox)) {
38 transform = s_identityTransform;
39 return;
40 }
41
42 transform = m_textBoxTransformations.get(textBox);
43 }
44
buildTextChunks(Vector<SVGInlineTextBox * > & lineLayoutBoxes)45 void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
46 {
47 if (lineLayoutBoxes.isEmpty())
48 return;
49
50 bool foundStart = false;
51 unsigned lastChunkStartPosition = 0;
52 unsigned boxPosition = 0;
53 unsigned boxCount = lineLayoutBoxes.size();
54 for (; boxPosition < boxCount; ++boxPosition) {
55 SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition];
56 if (!textBox->startsNewTextChunk())
57 continue;
58
59 if (!foundStart) {
60 lastChunkStartPosition = boxPosition;
61 foundStart = true;
62 } else {
63 ASSERT(boxPosition > lastChunkStartPosition);
64 addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
65 lastChunkStartPosition = boxPosition;
66 }
67 }
68
69 if (!foundStart)
70 return;
71
72 if (boxPosition - lastChunkStartPosition > 0)
73 addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
74 }
75
layoutTextChunks(Vector<SVGInlineTextBox * > & lineLayoutBoxes)76 void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
77 {
78 buildTextChunks(lineLayoutBoxes);
79 if (m_textChunks.isEmpty())
80 return;
81
82 unsigned chunkCount = m_textChunks.size();
83 for (unsigned i = 0; i < chunkCount; ++i)
84 processTextChunk(m_textChunks[i]);
85
86 m_textChunks.clear();
87 }
88
addTextChunk(Vector<SVGInlineTextBox * > & lineLayoutBoxes,unsigned boxStart,unsigned boxCount)89 void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount)
90 {
91 SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart];
92 ASSERT(textBox);
93
94 RenderSVGInlineText& textRenderer = toRenderSVGInlineText(textBox->textRenderer());
95
96 const RenderStyle* style = toRenderSVGInlineText(textBox->textRenderer()).style();
97 ASSERT(style);
98
99 const SVGRenderStyle* svgStyle = style->svgStyle();
100 ASSERT(svgStyle);
101
102 // Build chunk style flags.
103 unsigned chunkStyle = SVGTextChunk::DefaultStyle;
104
105 // Handle 'direction' property.
106 if (!style->isLeftToRightDirection())
107 chunkStyle |= SVGTextChunk::RightToLeftText;
108
109 // Handle 'writing-mode' property.
110 if (svgStyle->isVerticalWritingMode())
111 chunkStyle |= SVGTextChunk::VerticalText;
112
113 // Handle 'text-anchor' property.
114 switch (svgStyle->textAnchor()) {
115 case TA_START:
116 break;
117 case TA_MIDDLE:
118 chunkStyle |= SVGTextChunk::MiddleAnchor;
119 break;
120 case TA_END:
121 chunkStyle |= SVGTextChunk::EndAnchor;
122 break;
123 };
124
125 // Handle 'lengthAdjust' property.
126 float desiredTextLength = 0;
127 if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer.parent())) {
128 SVGLengthContext lengthContext(textContentElement);
129 if (textContentElement->textLengthIsSpecifiedByUser())
130 desiredTextLength = textContentElement->textLength()->currentValue()->value(lengthContext);
131 else
132 desiredTextLength = 0;
133
134 switch (textContentElement->lengthAdjust()->currentValue()->enumValue()) {
135 case SVGLengthAdjustUnknown:
136 break;
137 case SVGLengthAdjustSpacing:
138 chunkStyle |= SVGTextChunk::LengthAdjustSpacing;
139 break;
140 case SVGLengthAdjustSpacingAndGlyphs:
141 chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs;
142 break;
143 };
144 }
145
146 SVGTextChunk chunk(chunkStyle, desiredTextLength);
147
148 Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
149 for (unsigned i = boxStart; i < boxStart + boxCount; ++i)
150 boxes.append(lineLayoutBoxes[i]);
151
152 m_textChunks.append(chunk);
153 }
154
processTextChunk(const SVGTextChunk & chunk)155 void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk)
156 {
157 bool processTextLength = chunk.hasDesiredTextLength();
158 bool processTextAnchor = chunk.hasTextAnchor();
159 if (!processTextAnchor && !processTextLength)
160 return;
161
162 const Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
163 unsigned boxCount = boxes.size();
164 if (!boxCount)
165 return;
166
167 // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes).
168 float chunkLength = 0;
169 unsigned chunkCharacters = 0;
170 chunk.calculateLength(chunkLength, chunkCharacters);
171
172 bool isVerticalText = chunk.isVerticalText();
173 if (processTextLength) {
174 if (chunk.hasLengthAdjustSpacing()) {
175 float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters;
176 unsigned atCharacter = 0;
177 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
178 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
179 if (fragments.isEmpty())
180 continue;
181 processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter);
182 }
183 } else {
184 ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs());
185 float textLengthScale = chunk.desiredTextLength() / chunkLength;
186 AffineTransform spacingAndGlyphsTransform;
187
188 bool foundFirstFragment = false;
189 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
190 SVGInlineTextBox* textBox = boxes[boxPosition];
191 Vector<SVGTextFragment>& fragments = textBox->textFragments();
192 if (fragments.isEmpty())
193 continue;
194
195 if (!foundFirstFragment) {
196 foundFirstFragment = true;
197 buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform);
198 }
199
200 m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform);
201 }
202 }
203 }
204
205 if (!processTextAnchor)
206 return;
207
208 // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift.
209 if (processTextLength && chunk.hasLengthAdjustSpacing()) {
210 chunkLength = 0;
211 chunkCharacters = 0;
212 chunk.calculateLength(chunkLength, chunkCharacters);
213 }
214
215 float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength);
216 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
217 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
218 if (fragments.isEmpty())
219 continue;
220 processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments);
221 }
222 }
223
processTextLengthSpacingCorrection(bool isVerticalText,float textLengthShift,Vector<SVGTextFragment> & fragments,unsigned & atCharacter)224 void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter)
225 {
226 unsigned fragmentCount = fragments.size();
227 for (unsigned i = 0; i < fragmentCount; ++i) {
228 SVGTextFragment& fragment = fragments[i];
229
230 if (isVerticalText)
231 fragment.y += textLengthShift * atCharacter;
232 else
233 fragment.x += textLengthShift * atCharacter;
234
235 atCharacter += fragment.length;
236 }
237 }
238
processTextAnchorCorrection(bool isVerticalText,float textAnchorShift,Vector<SVGTextFragment> & fragments)239 void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments)
240 {
241 unsigned fragmentCount = fragments.size();
242 for (unsigned i = 0; i < fragmentCount; ++i) {
243 SVGTextFragment& fragment = fragments[i];
244
245 if (isVerticalText)
246 fragment.y += textAnchorShift;
247 else
248 fragment.x += textAnchorShift;
249 }
250 }
251
buildSpacingAndGlyphsTransform(bool isVerticalText,float scale,const SVGTextFragment & fragment,AffineTransform & spacingAndGlyphsTransform)252 void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform)
253 {
254 spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
255
256 if (isVerticalText)
257 spacingAndGlyphsTransform.scaleNonUniform(1, scale);
258 else
259 spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
260
261 spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
262 }
263
264 }
265