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