1/*
2 * Copyright (C) 2004, 2005, 2008 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
4 * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
5 * Copyright (C) 2008 Apple Inc. All rights reserved.
6 * Copyright (C) Research In Motion Limited 2012. All rights reserved.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB.  If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24#include "config.h"
25
26#include "core/svg/SVGTransformList.h"
27
28#include "core/SVGNames.h"
29#include "core/svg/SVGAnimateTransformElement.h"
30#include "core/svg/SVGAnimatedNumber.h"
31#include "core/svg/SVGParserUtilities.h"
32#include "core/svg/SVGTransformDistance.h"
33#include "wtf/text/StringBuilder.h"
34#include "wtf/text/WTFString.h"
35
36namespace blink {
37
38inline PassRefPtr<SVGTransformList> toSVGTransformList(PassRefPtr<SVGPropertyBase> passBase)
39{
40    RefPtr<SVGPropertyBase> base = passBase;
41    ASSERT(base->type() == SVGTransformList::classType());
42    return static_pointer_cast<SVGTransformList>(base.release());
43}
44
45SVGTransformList::SVGTransformList()
46{
47}
48
49SVGTransformList::~SVGTransformList()
50{
51}
52
53PassRefPtr<SVGTransform> SVGTransformList::consolidate()
54{
55    AffineTransform matrix;
56    if (!concatenate(matrix))
57        return SVGTransform::create();
58
59    RefPtr<SVGTransform> transform = SVGTransform::create(matrix);
60    clear();
61    return appendItem(transform);
62}
63
64bool SVGTransformList::concatenate(AffineTransform& result) const
65{
66    if (isEmpty())
67        return false;
68
69    ConstIterator it = begin();
70    ConstIterator itEnd = end();
71    for (; it != itEnd; ++it)
72        result *= it->matrix();
73
74    return true;
75}
76
77namespace {
78
79template<typename CharType>
80int parseTransformParamList(const CharType*& ptr, const CharType* end, float* values, int required, int optional)
81{
82    int parsedParams = 0;
83    int maxPossibleParams = required + optional;
84
85    bool trailingDelimiter = false;
86
87    skipOptionalSVGSpaces(ptr, end);
88    while (parsedParams < maxPossibleParams) {
89        if (!parseNumber(ptr, end, values[parsedParams], DisallowWhitespace))
90            break;
91
92        ++parsedParams;
93
94        if (skipOptionalSVGSpaces(ptr, end) && *ptr == ',') {
95            ++ptr;
96            skipOptionalSVGSpaces(ptr, end);
97
98            trailingDelimiter = true;
99        } else {
100            trailingDelimiter = false;
101        }
102    }
103
104    if (trailingDelimiter || !(parsedParams == required || parsedParams == maxPossibleParams))
105        return -1;
106
107    return parsedParams;
108}
109
110// These should be kept in sync with enum SVGTransformType
111static const int requiredValuesForType[] =  {0, 6, 1, 1, 1, 1, 1};
112static const int optionalValuesForType[] =  {0, 0, 1, 1, 2, 0, 0};
113
114template<typename CharType>
115PassRefPtr<SVGTransform> parseTransformOfType(unsigned type, const CharType*& ptr, const CharType* end)
116{
117    if (type == SVG_TRANSFORM_UNKNOWN)
118        return nullptr;
119
120    int valueCount = 0;
121    float values[] = {0, 0, 0, 0, 0, 0};
122    if ((valueCount = parseTransformParamList(ptr, end, values, requiredValuesForType[type], optionalValuesForType[type])) < 0) {
123        return nullptr;
124    }
125
126    RefPtr<SVGTransform> transform = SVGTransform::create();
127
128    switch (type) {
129    case SVG_TRANSFORM_SKEWX:
130        transform->setSkewX(values[0]);
131        break;
132    case SVG_TRANSFORM_SKEWY:
133        transform->setSkewY(values[0]);
134        break;
135    case SVG_TRANSFORM_SCALE:
136        if (valueCount == 1) // Spec: if only one param given, assume uniform scaling
137            transform->setScale(values[0], values[0]);
138        else
139            transform->setScale(values[0], values[1]);
140        break;
141    case SVG_TRANSFORM_TRANSLATE:
142        if (valueCount == 1) // Spec: if only one param given, assume 2nd param to be 0
143            transform->setTranslate(values[0], 0);
144        else
145            transform->setTranslate(values[0], values[1]);
146        break;
147    case SVG_TRANSFORM_ROTATE:
148        if (valueCount == 1)
149            transform->setRotate(values[0], 0, 0);
150        else
151            transform->setRotate(values[0], values[1], values[2]);
152        break;
153    case SVG_TRANSFORM_MATRIX:
154        transform->setMatrix(AffineTransform(values[0], values[1], values[2], values[3], values[4], values[5]));
155        break;
156    }
157
158    return transform.release();
159}
160
161}
162
163template<typename CharType>
164bool SVGTransformList::parseInternal(const CharType*& ptr, const CharType* end)
165{
166    clear();
167
168    bool delimParsed = false;
169    while (ptr < end) {
170        delimParsed = false;
171        SVGTransformType transformType = SVG_TRANSFORM_UNKNOWN;
172        skipOptionalSVGSpaces(ptr, end);
173
174        if (!parseAndSkipTransformType(ptr, end, transformType))
175            return false;
176
177        if (!skipOptionalSVGSpaces(ptr, end) || *ptr != '(')
178            return false;
179        ptr++;
180
181        RefPtr<SVGTransform> transform = parseTransformOfType(transformType, ptr, end);
182        if (!transform)
183            return false;
184
185        if (!skipOptionalSVGSpaces(ptr, end) || *ptr != ')')
186            return false;
187        ptr++;
188
189        append(transform.release());
190
191        skipOptionalSVGSpaces(ptr, end);
192        if (ptr < end && *ptr == ',') {
193            delimParsed = true;
194            ++ptr;
195            skipOptionalSVGSpaces(ptr, end);
196        }
197    }
198
199    return !delimParsed;
200}
201
202bool SVGTransformList::parse(const UChar*& ptr, const UChar* end)
203{
204    return parseInternal(ptr, end);
205}
206
207bool SVGTransformList::parse(const LChar*& ptr, const LChar* end)
208{
209    return parseInternal(ptr, end);
210}
211
212String SVGTransformList::valueAsString() const
213{
214    StringBuilder builder;
215
216    ConstIterator it = begin();
217    ConstIterator itEnd = end();
218    while (it != itEnd) {
219        builder.append(it->valueAsString());
220        ++it;
221        if (it != itEnd)
222            builder.append(' ');
223    }
224
225    return builder.toString();
226}
227
228void SVGTransformList::setValueAsString(const String& value, ExceptionState& exceptionState)
229{
230    if (value.isEmpty()) {
231        clear();
232        return;
233    }
234
235    bool valid = false;
236    if (value.is8Bit()) {
237        const LChar* ptr = value.characters8();
238        const LChar* end = ptr + value.length();
239        valid = parse(ptr, end);
240    } else {
241        const UChar* ptr = value.characters16();
242        const UChar* end = ptr + value.length();
243        valid = parse(ptr, end);
244    }
245
246    if (!valid) {
247        clear();
248        exceptionState.throwDOMException(SyntaxError, "Problem parsing transform list=\""+value+"\"");
249    }
250}
251
252PassRefPtr<SVGPropertyBase> SVGTransformList::cloneForAnimation(const String& value) const
253{
254    ASSERT_NOT_REACHED();
255    return nullptr;
256}
257
258PassRefPtr<SVGTransformList> SVGTransformList::create(SVGTransformType transformType, const String& value)
259{
260    RefPtr<SVGTransform> transform;
261    if (value.isEmpty()) {
262    } else if (value.is8Bit()) {
263        const LChar* ptr = value.characters8();
264        const LChar* end = ptr + value.length();
265        transform = parseTransformOfType(transformType, ptr, end);
266    } else {
267        const UChar* ptr = value.characters16();
268        const UChar* end = ptr + value.length();
269        transform = parseTransformOfType(transformType, ptr, end);
270    }
271
272    RefPtr<SVGTransformList> svgTransformList = SVGTransformList::create();
273    if (transform)
274        svgTransformList->append(transform);
275    return svgTransformList.release();
276}
277
278void SVGTransformList::add(PassRefPtrWillBeRawPtr<SVGPropertyBase> other, SVGElement* contextElement)
279{
280    if (isEmpty())
281        return;
282
283    RefPtr<SVGTransformList> otherList = toSVGTransformList(other);
284    if (length() != otherList->length())
285        return;
286
287    ASSERT(length() == 1);
288    RefPtr<SVGTransform> fromTransform = at(0);
289    RefPtr<SVGTransform> toTransform = otherList->at(0);
290
291    ASSERT(fromTransform->transformType() == toTransform->transformType());
292    clear();
293    append(SVGTransformDistance::addSVGTransforms(fromTransform, toTransform));
294}
295
296void SVGTransformList::calculateAnimatedValue(SVGAnimationElement* animationElement, float percentage, unsigned repeatCount, PassRefPtr<SVGPropertyBase> fromValue, PassRefPtr<SVGPropertyBase> toValue, PassRefPtr<SVGPropertyBase> toAtEndOfDurationValue, SVGElement* contextElement)
297{
298    ASSERT(animationElement);
299    bool isToAnimation = animationElement->animationMode() == ToAnimation;
300
301    // Spec: To animations provide specific functionality to get a smooth change from the underlying value to the
302    // ‘to’ attribute value, which conflicts mathematically with the requirement for additive transform animations
303    // to be post-multiplied. As a consequence, in SVG 1.1 the behavior of to animations for ‘animateTransform’ is undefined
304    // FIXME: This is not taken into account yet.
305    RefPtr<SVGTransformList> fromList = isToAnimation ? this : toSVGTransformList(fromValue);
306    RefPtr<SVGTransformList> toList = toSVGTransformList(toValue);
307    RefPtr<SVGTransformList> toAtEndOfDurationList = toSVGTransformList(toAtEndOfDurationValue);
308
309    size_t toListSize = toList->length();
310    if (!toListSize)
311        return;
312
313    // Get a reference to the from value before potentially cleaning it out (in the case of a To animation.)
314    RefPtr<SVGTransform> toTransform = toList->at(0);
315    RefPtr<SVGTransform> effectiveFrom;
316    // If there's an existing 'from'/underlying value of the same type use that, else use a "zero transform".
317    if (fromList->length() && fromList->at(0)->transformType() == toTransform->transformType())
318        effectiveFrom = fromList->at(0);
319    else
320        effectiveFrom = SVGTransform::create(toTransform->transformType(), SVGTransform::ConstructZeroTransform);
321
322    // Never resize the animatedTransformList to the toList size, instead either clear the list or append to it.
323    if (!isEmpty() && (!animationElement->isAdditive() || isToAnimation))
324        clear();
325
326    RefPtr<SVGTransform> currentTransform = SVGTransformDistance(effectiveFrom, toTransform).scaledDistance(percentage).addToSVGTransform(effectiveFrom);
327    if (animationElement->isAccumulated() && repeatCount) {
328        RefPtr<SVGTransform> effectiveToAtEnd = !toAtEndOfDurationList->isEmpty() ? toAtEndOfDurationList->at(0) : SVGTransform::create(toTransform->transformType(), SVGTransform::ConstructZeroTransform);
329        append(SVGTransformDistance::addSVGTransforms(currentTransform, effectiveToAtEnd, repeatCount));
330    } else {
331        append(currentTransform);
332    }
333}
334
335float SVGTransformList::calculateDistance(PassRefPtr<SVGPropertyBase> toValue, SVGElement*)
336{
337    // FIXME: This is not correct in all cases. The spec demands that each component (translate x and y for example)
338    // is paced separately. To implement this we need to treat each component as individual animation everywhere.
339
340    RefPtr<SVGTransformList> toList = toSVGTransformList(toValue);
341    if (isEmpty() || length() != toList->length())
342        return -1;
343
344    ASSERT(length() == 1);
345    if (at(0)->transformType() == toList->at(0)->transformType())
346        return -1;
347
348    // Spec: http://www.w3.org/TR/SVG/animate.html#complexDistances
349    // Paced animations assume a notion of distance between the various animation values defined by the ‘to’, ‘from’, ‘by’ and ‘values’ attributes.
350    // Distance is defined only for scalar types (such as <length>), colors and the subset of transformation types that are supported by ‘animateTransform’.
351    return SVGTransformDistance(at(0), toList->at(0)).distance();
352}
353
354}
355