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#include "SVGLengthContext.h"
29
30namespace WebCore {
31
32SVGTextChunkBuilder::SVGTextChunkBuilder()
33{
34}
35
36void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const
37{
38    DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ());
39    if (!m_textBoxTransformations.contains(textBox)) {
40        transform = s_identityTransform;
41        return;
42    }
43
44    transform = m_textBoxTransformations.get(textBox);
45}
46
47void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
48{
49    if (lineLayoutBoxes.isEmpty())
50        return;
51
52    bool foundStart = false;
53    unsigned lastChunkStartPosition = 0;
54    unsigned boxPosition = 0;
55    unsigned boxCount = lineLayoutBoxes.size();
56    for (; boxPosition < boxCount; ++boxPosition) {
57        SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition];
58        if (!textBox->startsNewTextChunk())
59            continue;
60
61        if (!foundStart) {
62            lastChunkStartPosition = boxPosition;
63            foundStart = true;
64        } else {
65            ASSERT(boxPosition > lastChunkStartPosition);
66            addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
67            lastChunkStartPosition = boxPosition;
68        }
69    }
70
71    if (!foundStart)
72        return;
73
74    if (boxPosition - lastChunkStartPosition > 0)
75        addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
76}
77
78void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
79{
80    buildTextChunks(lineLayoutBoxes);
81    if (m_textChunks.isEmpty())
82        return;
83
84    unsigned chunkCount = m_textChunks.size();
85    for (unsigned i = 0; i < chunkCount; ++i)
86        processTextChunk(m_textChunks[i]);
87
88    m_textChunks.clear();
89}
90
91void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount)
92{
93    SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart];
94    ASSERT(textBox);
95
96    RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer());
97    ASSERT(textRenderer);
98
99    const RenderStyle* style = textRenderer->style();
100    ASSERT(style);
101
102    const SVGRenderStyle* svgStyle = style->svgStyle();
103    ASSERT(svgStyle);
104
105    // Build chunk style flags.
106    unsigned chunkStyle = SVGTextChunk::DefaultStyle;
107
108    // Handle 'direction' property.
109    if (!style->isLeftToRightDirection())
110        chunkStyle |= SVGTextChunk::RightToLeftText;
111
112    // Handle 'writing-mode' property.
113    if (svgStyle->isVerticalWritingMode())
114        chunkStyle |= SVGTextChunk::VerticalText;
115
116    // Handle 'text-anchor' property.
117    switch (svgStyle->textAnchor()) {
118    case TA_START:
119        break;
120    case TA_MIDDLE:
121        chunkStyle |= SVGTextChunk::MiddleAnchor;
122        break;
123    case TA_END:
124        chunkStyle |= SVGTextChunk::EndAnchor;
125        break;
126    };
127
128    // Handle 'lengthAdjust' property.
129    float desiredTextLength = 0;
130    if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer->parent())) {
131        SVGLengthContext lengthContext(textContentElement);
132        desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
133
134        switch (textContentElement->lengthAdjust()) {
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
155void 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
224void 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
239void 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
252void 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
266#endif // ENABLE(SVG)
267