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 #if ENABLE(SVG)
23 #include "SVGTextChunkBuilder.h"
24
25 #include "RenderSVGInlineText.h"
26 #include "SVGElement.h"
27 #include "SVGInlineTextBox.h"
28
29 namespace WebCore {
30
SVGTextChunkBuilder()31 SVGTextChunkBuilder::SVGTextChunkBuilder()
32 {
33 }
34
transformationForTextBox(SVGInlineTextBox * textBox,AffineTransform & transform) const35 void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const
36 {
37 DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ());
38 if (!m_textBoxTransformations.contains(textBox)) {
39 transform = s_identityTransform;
40 return;
41 }
42
43 transform = m_textBoxTransformations.get(textBox);
44 }
45
buildTextChunks(Vector<SVGInlineTextBox * > & lineLayoutBoxes)46 void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
47 {
48 if (lineLayoutBoxes.isEmpty())
49 return;
50
51 bool foundStart = false;
52 unsigned lastChunkStartPosition = 0;
53 unsigned boxPosition = 0;
54 unsigned boxCount = lineLayoutBoxes.size();
55 for (; boxPosition < boxCount; ++boxPosition) {
56 SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition];
57 if (!textBox->startsNewTextChunk())
58 continue;
59
60 if (!foundStart) {
61 lastChunkStartPosition = boxPosition;
62 foundStart = true;
63 } else {
64 ASSERT(boxPosition > lastChunkStartPosition);
65 addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
66 lastChunkStartPosition = boxPosition;
67 }
68 }
69
70 if (!foundStart)
71 return;
72
73 if (boxPosition - lastChunkStartPosition > 0)
74 addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
75 }
76
layoutTextChunks(Vector<SVGInlineTextBox * > & lineLayoutBoxes)77 void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
78 {
79 buildTextChunks(lineLayoutBoxes);
80 if (m_textChunks.isEmpty())
81 return;
82
83 unsigned chunkCount = m_textChunks.size();
84 for (unsigned i = 0; i < chunkCount; ++i)
85 processTextChunk(m_textChunks[i]);
86
87 m_textChunks.clear();
88 }
89
addTextChunk(Vector<SVGInlineTextBox * > & lineLayoutBoxes,unsigned boxStart,unsigned boxCount)90 void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount)
91 {
92 SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart];
93 ASSERT(textBox);
94
95 RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer());
96 ASSERT(textRenderer);
97
98 const RenderStyle* style = textRenderer->style();
99 ASSERT(style);
100
101 const SVGRenderStyle* svgStyle = style->svgStyle();
102 ASSERT(svgStyle);
103
104 // Build chunk style flags.
105 unsigned chunkStyle = SVGTextChunk::DefaultStyle;
106
107 // Handle 'direction' property.
108 if (!style->isLeftToRightDirection())
109 chunkStyle |= SVGTextChunk::RightToLeftText;
110
111 // Handle 'writing-mode' property.
112 if (svgStyle->isVerticalWritingMode())
113 chunkStyle |= SVGTextChunk::VerticalText;
114
115 // Handle 'text-anchor' property.
116 switch (svgStyle->textAnchor()) {
117 case TA_START:
118 break;
119 case TA_MIDDLE:
120 chunkStyle |= SVGTextChunk::MiddleAnchor;
121 break;
122 case TA_END:
123 chunkStyle |= SVGTextChunk::EndAnchor;
124 break;
125 };
126
127 // Handle 'lengthAdjust' property.
128 float desiredTextLength = 0;
129 if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer->parent())) {
130 desiredTextLength = textContentElement->specifiedTextLength().value(textContentElement);
131
132 switch (static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust())) {
133 case SVGTextContentElement::LENGTHADJUST_UNKNOWN:
134 break;
135 case SVGTextContentElement::LENGTHADJUST_SPACING:
136 chunkStyle |= SVGTextChunk::LengthAdjustSpacing;
137 break;
138 case SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS:
139 chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs;
140 break;
141 };
142 }
143
144 SVGTextChunk chunk(chunkStyle, desiredTextLength);
145
146 Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
147 for (unsigned i = boxStart; i < boxStart + boxCount; ++i)
148 boxes.append(lineLayoutBoxes[i]);
149
150 m_textChunks.append(chunk);
151 }
152
processTextChunk(const SVGTextChunk & chunk)153 void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk)
154 {
155 bool processTextLength = chunk.hasDesiredTextLength();
156 bool processTextAnchor = chunk.hasTextAnchor();
157 if (!processTextAnchor && !processTextLength)
158 return;
159
160 const Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
161 unsigned boxCount = boxes.size();
162 if (!boxCount)
163 return;
164
165 // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes).
166 float chunkLength = 0;
167 unsigned chunkCharacters = 0;
168 chunk.calculateLength(chunkLength, chunkCharacters);
169
170 bool isVerticalText = chunk.isVerticalText();
171 if (processTextLength) {
172 if (chunk.hasLengthAdjustSpacing()) {
173 float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters;
174 unsigned atCharacter = 0;
175 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
176 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
177 if (fragments.isEmpty())
178 continue;
179 processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter);
180 }
181 } else {
182 ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs());
183 float textLengthScale = chunk.desiredTextLength() / chunkLength;
184 AffineTransform spacingAndGlyphsTransform;
185
186 bool foundFirstFragment = false;
187 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
188 SVGInlineTextBox* textBox = boxes[boxPosition];
189 Vector<SVGTextFragment>& fragments = textBox->textFragments();
190 if (fragments.isEmpty())
191 continue;
192
193 if (!foundFirstFragment) {
194 foundFirstFragment = true;
195 buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform);
196 }
197
198 m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform);
199 }
200 }
201 }
202
203 if (!processTextAnchor)
204 return;
205
206 // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift.
207 if (processTextLength && chunk.hasLengthAdjustSpacing()) {
208 chunkLength = 0;
209 chunkCharacters = 0;
210 chunk.calculateLength(chunkLength, chunkCharacters);
211 }
212
213 float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength);
214 for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
215 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
216 if (fragments.isEmpty())
217 continue;
218 processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments);
219 }
220 }
221
processTextLengthSpacingCorrection(bool isVerticalText,float textLengthShift,Vector<SVGTextFragment> & fragments,unsigned & atCharacter)222 void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter)
223 {
224 unsigned fragmentCount = fragments.size();
225 for (unsigned i = 0; i < fragmentCount; ++i) {
226 SVGTextFragment& fragment = fragments[i];
227
228 if (isVerticalText)
229 fragment.y += textLengthShift * atCharacter;
230 else
231 fragment.x += textLengthShift * atCharacter;
232
233 atCharacter += fragment.length;
234 }
235 }
236
processTextAnchorCorrection(bool isVerticalText,float textAnchorShift,Vector<SVGTextFragment> & fragments)237 void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments)
238 {
239 unsigned fragmentCount = fragments.size();
240 for (unsigned i = 0; i < fragmentCount; ++i) {
241 SVGTextFragment& fragment = fragments[i];
242
243 if (isVerticalText)
244 fragment.y += textAnchorShift;
245 else
246 fragment.x += textAnchorShift;
247 }
248 }
249
buildSpacingAndGlyphsTransform(bool isVerticalText,float scale,const SVGTextFragment & fragment,AffineTransform & spacingAndGlyphsTransform)250 void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform)
251 {
252 spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
253
254 if (isVerticalText)
255 spacingAndGlyphsTransform.scaleNonUniform(1, scale);
256 else
257 spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
258
259 spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
260 }
261
262 }
263
264 #endif // ENABLE(SVG)
265