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