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) 2010 Dirk Schulze <krit@webkit.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB.  If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23#include "core/svg/SVGPreserveAspectRatio.h"
24
25#include "bindings/core/v8/ExceptionState.h"
26#include "bindings/core/v8/ExceptionStatePlaceholder.h"
27#include "core/dom/ExceptionCode.h"
28#include "core/svg/SVGAnimationElement.h"
29#include "core/svg/SVGParserUtilities.h"
30#include "platform/geometry/FloatRect.h"
31#include "platform/transforms/AffineTransform.h"
32#include "wtf/text/WTFString.h"
33
34namespace blink {
35
36SVGPreserveAspectRatio::SVGPreserveAspectRatio()
37{
38    setDefault();
39}
40
41void SVGPreserveAspectRatio::setDefault()
42{
43    m_align = SVG_PRESERVEASPECTRATIO_XMIDYMID;
44    m_meetOrSlice = SVG_MEETORSLICE_MEET;
45}
46
47PassRefPtr<SVGPreserveAspectRatio> SVGPreserveAspectRatio::clone() const
48{
49    RefPtr<SVGPreserveAspectRatio> preserveAspectRatio = create();
50
51    preserveAspectRatio->m_align = m_align;
52    preserveAspectRatio->m_meetOrSlice = m_meetOrSlice;
53
54    return preserveAspectRatio.release();
55}
56
57template<typename CharType>
58bool SVGPreserveAspectRatio::parseInternal(const CharType*& ptr, const CharType* end, bool validate)
59{
60    SVGPreserveAspectRatioType align = SVG_PRESERVEASPECTRATIO_XMIDYMID;
61    SVGMeetOrSliceType meetOrSlice = SVG_MEETORSLICE_MEET;
62
63    setAlign(align);
64    setMeetOrSlice(meetOrSlice);
65
66    if (!skipOptionalSVGSpaces(ptr, end))
67        return false;
68
69    if (*ptr == 'd') {
70        if (!skipString(ptr, end, "defer"))
71            return false;
72
73        // FIXME: We just ignore the "defer" here.
74        if (ptr == end)
75            return true;
76
77        if (!skipOptionalSVGSpaces(ptr, end))
78            return false;
79    }
80
81    if (*ptr == 'n') {
82        if (!skipString(ptr, end, "none"))
83            return false;
84        align = SVG_PRESERVEASPECTRATIO_NONE;
85        skipOptionalSVGSpaces(ptr, end);
86    } else if (*ptr == 'x') {
87        if ((end - ptr) < 8)
88            return false;
89        if (ptr[1] != 'M' || ptr[4] != 'Y' || ptr[5] != 'M')
90            return false;
91        if (ptr[2] == 'i') {
92            if (ptr[3] == 'n') {
93                if (ptr[6] == 'i') {
94                    if (ptr[7] == 'n')
95                        align = SVG_PRESERVEASPECTRATIO_XMINYMIN;
96                    else if (ptr[7] == 'd')
97                        align = SVG_PRESERVEASPECTRATIO_XMINYMID;
98                    else
99                        return false;
100                } else if (ptr[6] == 'a' && ptr[7] == 'x') {
101                    align = SVG_PRESERVEASPECTRATIO_XMINYMAX;
102                } else {
103                    return false;
104                }
105            } else if (ptr[3] == 'd') {
106                if (ptr[6] == 'i') {
107                    if (ptr[7] == 'n')
108                        align = SVG_PRESERVEASPECTRATIO_XMIDYMIN;
109                    else if (ptr[7] == 'd')
110                        align = SVG_PRESERVEASPECTRATIO_XMIDYMID;
111                    else
112                        return false;
113                } else if (ptr[6] == 'a' && ptr[7] == 'x') {
114                    align = SVG_PRESERVEASPECTRATIO_XMIDYMAX;
115                } else {
116                    return false;
117                }
118            } else {
119                return false;
120            }
121        } else if (ptr[2] == 'a' && ptr[3] == 'x') {
122            if (ptr[6] == 'i') {
123                if (ptr[7] == 'n')
124                    align = SVG_PRESERVEASPECTRATIO_XMAXYMIN;
125                else if (ptr[7] == 'd')
126                    align = SVG_PRESERVEASPECTRATIO_XMAXYMID;
127                else
128                    return false;
129            } else if (ptr[6] == 'a' && ptr[7] == 'x') {
130                align = SVG_PRESERVEASPECTRATIO_XMAXYMAX;
131            } else {
132                return false;
133            }
134        } else {
135            return false;
136        }
137        ptr += 8;
138        skipOptionalSVGSpaces(ptr, end);
139    } else {
140        return false;
141    }
142
143    if (ptr < end) {
144        if (*ptr == 'm') {
145            if (!skipString(ptr, end, "meet"))
146                return false;
147            skipOptionalSVGSpaces(ptr, end);
148        } else if (*ptr == 's') {
149            if (!skipString(ptr, end, "slice"))
150                return false;
151            skipOptionalSVGSpaces(ptr, end);
152            if (align != SVG_PRESERVEASPECTRATIO_NONE)
153                meetOrSlice = SVG_MEETORSLICE_SLICE;
154        }
155    }
156
157    if (end != ptr && validate)
158        return false;
159
160    setAlign(align);
161    setMeetOrSlice(meetOrSlice);
162
163    return true;
164}
165
166void SVGPreserveAspectRatio::setValueAsString(const String& string, ExceptionState& exceptionState)
167{
168    setDefault();
169
170    if (string.isEmpty())
171        return;
172
173    bool valid = false;
174    if (string.is8Bit()) {
175        const LChar* ptr = string.characters8();
176        const LChar* end = ptr + string.length();
177        valid = parseInternal(ptr, end, true);
178    } else {
179        const UChar* ptr = string.characters16();
180        const UChar* end = ptr + string.length();
181        valid = parseInternal(ptr, end, true);
182    }
183
184    if (!valid) {
185        exceptionState.throwDOMException(SyntaxError, "The value provided ('" + string + "') is invalid.");
186    }
187}
188
189bool SVGPreserveAspectRatio::parse(const LChar*& ptr, const LChar* end, bool validate)
190{
191    return parseInternal(ptr, end, validate);
192}
193
194bool SVGPreserveAspectRatio::parse(const UChar*& ptr, const UChar* end, bool validate)
195{
196    return parseInternal(ptr, end, validate);
197}
198
199void SVGPreserveAspectRatio::transformRect(FloatRect& destRect, FloatRect& srcRect)
200{
201    if (m_align == SVG_PRESERVEASPECTRATIO_NONE)
202        return;
203
204    FloatSize imageSize = srcRect.size();
205    float origDestWidth = destRect.width();
206    float origDestHeight = destRect.height();
207    switch (m_meetOrSlice) {
208    case SVGPreserveAspectRatio::SVG_MEETORSLICE_UNKNOWN:
209        break;
210    case SVGPreserveAspectRatio::SVG_MEETORSLICE_MEET: {
211        float widthToHeightMultiplier = srcRect.height() / srcRect.width();
212        if (origDestHeight > origDestWidth * widthToHeightMultiplier) {
213            destRect.setHeight(origDestWidth * widthToHeightMultiplier);
214            switch (m_align) {
215            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMID:
216            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID:
217            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMID:
218                destRect.setY(destRect.y() + origDestHeight / 2 - destRect.height() / 2);
219                break;
220            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMAX:
221            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMAX:
222            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMAX:
223                destRect.setY(destRect.y() + origDestHeight - destRect.height());
224                break;
225            default:
226                break;
227            }
228        }
229        if (origDestWidth > origDestHeight / widthToHeightMultiplier) {
230            destRect.setWidth(origDestHeight / widthToHeightMultiplier);
231            switch (m_align) {
232            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMIN:
233            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID:
234            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMAX:
235                destRect.setX(destRect.x() + origDestWidth / 2 - destRect.width() / 2);
236                break;
237            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMIN:
238            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMID:
239            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMAX:
240                destRect.setX(destRect.x() + origDestWidth - destRect.width());
241                break;
242            default:
243                break;
244            }
245        }
246        break;
247    }
248    case SVGPreserveAspectRatio::SVG_MEETORSLICE_SLICE: {
249        float widthToHeightMultiplier = srcRect.height() / srcRect.width();
250        // if the destination height is less than the height of the image we'll be drawing
251        if (origDestHeight < origDestWidth * widthToHeightMultiplier) {
252            float destToSrcMultiplier = srcRect.width() / destRect.width();
253            srcRect.setHeight(destRect.height() * destToSrcMultiplier);
254            switch (m_align) {
255            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMID:
256            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID:
257            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMID:
258                srcRect.setY(srcRect.y() + imageSize.height() / 2 - srcRect.height() / 2);
259                break;
260            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMAX:
261            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMAX:
262            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMAX:
263                srcRect.setY(srcRect.y() + imageSize.height() - srcRect.height());
264                break;
265            default:
266                break;
267            }
268        }
269        // if the destination width is less than the width of the image we'll be drawing
270        if (origDestWidth < origDestHeight / widthToHeightMultiplier) {
271            float destToSrcMultiplier = srcRect.height() / destRect.height();
272            srcRect.setWidth(destRect.width() * destToSrcMultiplier);
273            switch (m_align) {
274            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMIN:
275            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID:
276            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMAX:
277                srcRect.setX(srcRect.x() + imageSize.width() / 2 - srcRect.width() / 2);
278                break;
279            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMIN:
280            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMID:
281            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMAX:
282                srcRect.setX(srcRect.x() + imageSize.width() - srcRect.width());
283                break;
284            default:
285                break;
286            }
287        }
288        break;
289    }
290    }
291}
292
293AffineTransform SVGPreserveAspectRatio::getCTM(float logicalX, float logicalY, float logicalWidth, float logicalHeight, float physicalWidth, float physicalHeight) const
294{
295    ASSERT(logicalWidth);
296    ASSERT(logicalHeight);
297    ASSERT(physicalWidth);
298    ASSERT(physicalHeight);
299
300    AffineTransform transform;
301    if (m_align == SVG_PRESERVEASPECTRATIO_UNKNOWN)
302        return transform;
303
304    double extendedLogicalX = logicalX;
305    double extendedLogicalY = logicalY;
306    double extendedLogicalWidth = logicalWidth;
307    double extendedLogicalHeight = logicalHeight;
308    double extendedPhysicalWidth = physicalWidth;
309    double extendedPhysicalHeight = physicalHeight;
310    double logicalRatio = extendedLogicalWidth / extendedLogicalHeight;
311    double physicalRatio = extendedPhysicalWidth / extendedPhysicalHeight;
312
313    if (m_align == SVG_PRESERVEASPECTRATIO_NONE) {
314        transform.scaleNonUniform(extendedPhysicalWidth / extendedLogicalWidth, extendedPhysicalHeight / extendedLogicalHeight);
315        transform.translate(-extendedLogicalX, -extendedLogicalY);
316        return transform;
317    }
318
319    if ((logicalRatio < physicalRatio && (m_meetOrSlice == SVG_MEETORSLICE_MEET)) || (logicalRatio >= physicalRatio && (m_meetOrSlice == SVG_MEETORSLICE_SLICE))) {
320        transform.scaleNonUniform(extendedPhysicalHeight / extendedLogicalHeight, extendedPhysicalHeight / extendedLogicalHeight);
321
322        if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMINYMID || m_align == SVG_PRESERVEASPECTRATIO_XMINYMAX)
323            transform.translate(-extendedLogicalX, -extendedLogicalY);
324        else if (m_align == SVG_PRESERVEASPECTRATIO_XMIDYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMID || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMAX)
325            transform.translate(-extendedLogicalX - (extendedLogicalWidth - extendedPhysicalWidth * extendedLogicalHeight / extendedPhysicalHeight) / 2, -extendedLogicalY);
326        else
327            transform.translate(-extendedLogicalX - (extendedLogicalWidth - extendedPhysicalWidth * extendedLogicalHeight / extendedPhysicalHeight), -extendedLogicalY);
328
329        return transform;
330    }
331
332    transform.scaleNonUniform(extendedPhysicalWidth / extendedLogicalWidth, extendedPhysicalWidth / extendedLogicalWidth);
333
334    if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMAXYMIN)
335        transform.translate(-extendedLogicalX, -extendedLogicalY);
336    else if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMID || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMID || m_align == SVG_PRESERVEASPECTRATIO_XMAXYMID)
337        transform.translate(-extendedLogicalX, -extendedLogicalY - (extendedLogicalHeight - extendedPhysicalHeight * extendedLogicalWidth / extendedPhysicalWidth) / 2);
338    else
339        transform.translate(-extendedLogicalX, -extendedLogicalY - (extendedLogicalHeight - extendedPhysicalHeight * extendedLogicalWidth / extendedPhysicalWidth));
340
341    return transform;
342}
343
344String SVGPreserveAspectRatio::valueAsString() const
345{
346    String alignType;
347
348    switch (m_align) {
349    case SVG_PRESERVEASPECTRATIO_NONE:
350        alignType = "none";
351        break;
352    case SVG_PRESERVEASPECTRATIO_XMINYMIN:
353        alignType = "xMinYMin";
354        break;
355    case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
356        alignType = "xMidYMin";
357        break;
358    case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
359        alignType = "xMaxYMin";
360        break;
361    case SVG_PRESERVEASPECTRATIO_XMINYMID:
362        alignType = "xMinYMid";
363        break;
364    case SVG_PRESERVEASPECTRATIO_XMIDYMID:
365        alignType = "xMidYMid";
366        break;
367    case SVG_PRESERVEASPECTRATIO_XMAXYMID:
368        alignType = "xMaxYMid";
369        break;
370    case SVG_PRESERVEASPECTRATIO_XMINYMAX:
371        alignType = "xMinYMax";
372        break;
373    case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
374        alignType = "xMidYMax";
375        break;
376    case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
377        alignType = "xMaxYMax";
378        break;
379    case SVG_PRESERVEASPECTRATIO_UNKNOWN:
380        alignType = "unknown";
381        break;
382    };
383
384    switch (m_meetOrSlice) {
385    default:
386    case SVG_MEETORSLICE_UNKNOWN:
387        return alignType;
388    case SVG_MEETORSLICE_MEET:
389        return alignType + " meet";
390    case SVG_MEETORSLICE_SLICE:
391        return alignType + " slice";
392    }
393}
394
395void SVGPreserveAspectRatio::add(PassRefPtrWillBeRawPtr<SVGPropertyBase> other, SVGElement*)
396{
397    ASSERT_NOT_REACHED();
398}
399
400void SVGPreserveAspectRatio::calculateAnimatedValue(SVGAnimationElement* animationElement, float percentage, unsigned repeatCount, PassRefPtr<SVGPropertyBase> fromValue, PassRefPtr<SVGPropertyBase> toValue, PassRefPtr<SVGPropertyBase>, SVGElement*)
401{
402    ASSERT(animationElement);
403
404    bool useToValue;
405    animationElement->animateDiscreteType(percentage, false, true, useToValue);
406
407    RefPtr<SVGPreserveAspectRatio> preserveAspectRatioToUse = useToValue ? toSVGPreserveAspectRatio(toValue) : toSVGPreserveAspectRatio(fromValue);
408
409    m_align = preserveAspectRatioToUse->m_align;
410    m_meetOrSlice = preserveAspectRatioToUse->m_meetOrSlice;
411}
412
413float SVGPreserveAspectRatio::calculateDistance(PassRefPtr<SVGPropertyBase> toValue, SVGElement* contextElement)
414{
415    // No paced animations for SVGPreserveAspectRatio.
416    return -1;
417}
418
419}
420