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
24#if ENABLE(SVG)
25#include "SVGPreserveAspectRatio.h"
26
27#include "AffineTransform.h"
28#include "FloatRect.h"
29#include "SVGParserUtilities.h"
30#include <wtf/text/StringConcatenate.h>
31
32namespace WebCore {
33
34SVGPreserveAspectRatio::SVGPreserveAspectRatio()
35    : m_align(SVG_PRESERVEASPECTRATIO_XMIDYMID)
36    , m_meetOrSlice(SVG_MEETORSLICE_MEET)
37{
38}
39
40void SVGPreserveAspectRatio::setAlign(unsigned short align, ExceptionCode& ec)
41{
42    if (align == SVG_PRESERVEASPECTRATIO_UNKNOWN || align > SVG_PRESERVEASPECTRATIO_XMAXYMAX) {
43        ec = NOT_SUPPORTED_ERR;
44        return;
45    }
46
47    m_align = static_cast<SVGPreserveAspectRatioType>(align);
48}
49
50void SVGPreserveAspectRatio::setMeetOrSlice(unsigned short meetOrSlice, ExceptionCode& ec)
51{
52    if (meetOrSlice == SVG_MEETORSLICE_UNKNOWN || meetOrSlice > SVG_MEETORSLICE_SLICE) {
53        ec = NOT_SUPPORTED_ERR;
54        return;
55    }
56
57    m_meetOrSlice = static_cast<SVGMeetOrSliceType>(meetOrSlice);
58}
59
60SVGPreserveAspectRatio SVGPreserveAspectRatio::parsePreserveAspectRatio(const UChar*& currParam, const UChar* end, bool validate, bool& result)
61{
62    SVGPreserveAspectRatio aspectRatio;
63    aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_NONE;
64    aspectRatio.m_meetOrSlice = SVG_MEETORSLICE_MEET;
65    result = false;
66
67    // FIXME: Rewrite this parser, without gotos!
68    if (!skipOptionalSpaces(currParam, end))
69        goto bailOut;
70
71    if (*currParam == 'd') {
72        if (!skipString(currParam, end, "defer"))
73            goto bailOut;
74        // FIXME: We just ignore the "defer" here.
75        if (!skipOptionalSpaces(currParam, end))
76            goto bailOut;
77    }
78
79    if (*currParam == 'n') {
80        if (!skipString(currParam, end, "none"))
81            goto bailOut;
82        skipOptionalSpaces(currParam, end);
83    } else if (*currParam == 'x') {
84        if ((end - currParam) < 8)
85            goto bailOut;
86        if (currParam[1] != 'M' || currParam[4] != 'Y' || currParam[5] != 'M')
87            goto bailOut;
88        if (currParam[2] == 'i') {
89            if (currParam[3] == 'n') {
90                if (currParam[6] == 'i') {
91                    if (currParam[7] == 'n')
92                        aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_XMINYMIN;
93                    else if (currParam[7] == 'd')
94                        aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_XMINYMID;
95                    else
96                        goto bailOut;
97                } else if (currParam[6] == 'a' && currParam[7] == 'x')
98                     aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_XMINYMAX;
99                else
100                     goto bailOut;
101             } else if (currParam[3] == 'd') {
102                if (currParam[6] == 'i') {
103                    if (currParam[7] == 'n')
104                        aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_XMIDYMIN;
105                    else if (currParam[7] == 'd')
106                        aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_XMIDYMID;
107                    else
108                        goto bailOut;
109                } else if (currParam[6] == 'a' && currParam[7] == 'x')
110                    aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_XMIDYMAX;
111                else
112                    goto bailOut;
113            } else
114                goto bailOut;
115        } else if (currParam[2] == 'a' && currParam[3] == 'x') {
116            if (currParam[6] == 'i') {
117                if (currParam[7] == 'n')
118                    aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_XMAXYMIN;
119                else if (currParam[7] == 'd')
120                    aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_XMAXYMID;
121                else
122                    goto bailOut;
123            } else if (currParam[6] == 'a' && currParam[7] == 'x')
124                aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_XMAXYMAX;
125            else
126                goto bailOut;
127        } else
128            goto bailOut;
129        currParam += 8;
130        skipOptionalSpaces(currParam, end);
131    } else
132        goto bailOut;
133
134    if (currParam < end) {
135        if (*currParam == 'm') {
136            if (!skipString(currParam, end, "meet"))
137                goto bailOut;
138            skipOptionalSpaces(currParam, end);
139        } else if (*currParam == 's') {
140            if (!skipString(currParam, end, "slice"))
141                goto bailOut;
142            skipOptionalSpaces(currParam, end);
143            if (aspectRatio.m_align != SVG_PRESERVEASPECTRATIO_NONE)
144                aspectRatio.m_meetOrSlice = SVG_MEETORSLICE_SLICE;
145        }
146    }
147
148    if (end != currParam && validate) {
149bailOut:
150        // FIXME: Should the two values be set to UNKNOWN instead?
151        aspectRatio.m_align = SVG_PRESERVEASPECTRATIO_NONE;
152        aspectRatio.m_meetOrSlice = SVG_MEETORSLICE_MEET;
153    } else
154        result = true;
155
156    return aspectRatio;
157}
158
159void SVGPreserveAspectRatio::transformRect(FloatRect& destRect, FloatRect& srcRect)
160{
161    FloatSize imageSize = srcRect.size();
162    float origDestWidth = destRect.width();
163    float origDestHeight = destRect.height();
164    switch (m_meetOrSlice) {
165    case SVGPreserveAspectRatio::SVG_MEETORSLICE_UNKNOWN:
166        break;
167    case SVGPreserveAspectRatio::SVG_MEETORSLICE_MEET: {
168        float widthToHeightMultiplier = srcRect.height() / srcRect.width();
169        if (origDestHeight > origDestWidth * widthToHeightMultiplier) {
170            destRect.setHeight(origDestWidth * widthToHeightMultiplier);
171            switch (m_align) {
172            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMID:
173            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID:
174            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMID:
175                destRect.setY(destRect.y() + origDestHeight / 2 - destRect.height() / 2);
176                break;
177            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMAX:
178            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMAX:
179            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMAX:
180                destRect.setY(destRect.y() + origDestHeight - destRect.height());
181                break;
182            default:
183                break;
184            }
185        }
186        if (origDestWidth > origDestHeight / widthToHeightMultiplier) {
187            destRect.setWidth(origDestHeight / widthToHeightMultiplier);
188            switch (m_align) {
189            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMIN:
190            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID:
191            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMAX:
192                destRect.setX(destRect.x() + origDestWidth / 2 - destRect.width() / 2);
193                break;
194            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMIN:
195            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMID:
196            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMAX:
197                destRect.setX(destRect.x() + origDestWidth - destRect.width());
198                break;
199            default:
200                break;
201            }
202        }
203        break;
204    }
205    case SVGPreserveAspectRatio::SVG_MEETORSLICE_SLICE: {
206        float widthToHeightMultiplier = srcRect.height() / srcRect.width();
207        // if the destination height is less than the height of the image we'll be drawing
208        if (origDestHeight < origDestWidth * widthToHeightMultiplier) {
209            float destToSrcMultiplier = srcRect.width() / destRect.width();
210            srcRect.setHeight(destRect.height() * destToSrcMultiplier);
211            switch (m_align) {
212            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMID:
213            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID:
214            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMID:
215                srcRect.setY(destRect.y() + imageSize.height() / 2 - srcRect.height() / 2);
216                break;
217            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMINYMAX:
218            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMAX:
219            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMAX:
220                srcRect.setY(destRect.y() + imageSize.height() - srcRect.height());
221                break;
222            default:
223                break;
224            }
225        }
226        // if the destination width is less than the width of the image we'll be drawing
227        if (origDestWidth < origDestHeight / widthToHeightMultiplier) {
228            float destToSrcMultiplier = srcRect.height() / destRect.height();
229            srcRect.setWidth(destRect.width() * destToSrcMultiplier);
230            switch (m_align) {
231            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMIN:
232            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMID:
233            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMIDYMAX:
234                srcRect.setX(destRect.x() + imageSize.width() / 2 - srcRect.width() / 2);
235                break;
236            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMIN:
237            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMID:
238            case SVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_XMAXYMAX:
239                srcRect.setX(destRect.x() + imageSize.width() - srcRect.width());
240                break;
241            default:
242                break;
243            }
244        }
245        break;
246    }
247    }
248}
249
250AffineTransform SVGPreserveAspectRatio::getCTM(float logicX, float logicY, float logicWidth, float logicHeight, float physWidth, float physHeight) const
251{
252    AffineTransform transform;
253    if (m_align == SVG_PRESERVEASPECTRATIO_UNKNOWN)
254        return transform;
255
256    float logicalRatio = logicWidth / logicHeight;
257    float physRatio = physWidth / physHeight;
258
259    if (m_align == SVG_PRESERVEASPECTRATIO_NONE) {
260        transform.scaleNonUniform(physWidth / logicWidth, physHeight / logicHeight);
261        transform.translate(-logicX, -logicY);
262        return transform;
263    }
264
265    if ((logicalRatio < physRatio && (m_meetOrSlice == SVG_MEETORSLICE_MEET)) || (logicalRatio >= physRatio && (m_meetOrSlice == SVG_MEETORSLICE_SLICE))) {
266        transform.scaleNonUniform(physHeight / logicHeight, physHeight / logicHeight);
267
268        if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMINYMID || m_align == SVG_PRESERVEASPECTRATIO_XMINYMAX)
269            transform.translate(-logicX, -logicY);
270        else if (m_align == SVG_PRESERVEASPECTRATIO_XMIDYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMID || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMAX)
271            transform.translate(-logicX - (logicWidth - physWidth * logicHeight / physHeight) / 2, -logicY);
272        else
273            transform.translate(-logicX - (logicWidth - physWidth * logicHeight / physHeight), -logicY);
274
275        return transform;
276    }
277
278    transform.scaleNonUniform(physWidth / logicWidth, physWidth / logicWidth);
279
280    if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMAXYMIN)
281        transform.translate(-logicX, -logicY);
282    else if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMID || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMID || m_align == SVG_PRESERVEASPECTRATIO_XMAXYMID)
283        transform.translate(-logicX, -logicY - (logicHeight - physHeight * logicWidth / physWidth) / 2);
284    else
285        transform.translate(-logicX, -logicY - (logicHeight - physHeight * logicWidth / physWidth));
286
287    return transform;
288}
289
290String SVGPreserveAspectRatio::valueAsString() const
291{
292    String alignType;
293
294    switch (m_align) {
295    case SVG_PRESERVEASPECTRATIO_NONE:
296        alignType = "none";
297        break;
298    case SVG_PRESERVEASPECTRATIO_XMINYMIN:
299        alignType = "xMinYMin";
300        break;
301    case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
302        alignType = "xMidYMin";
303        break;
304    case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
305        alignType = "xMaxYMin";
306        break;
307    case SVG_PRESERVEASPECTRATIO_XMINYMID:
308        alignType = "xMinYMid";
309        break;
310    case SVG_PRESERVEASPECTRATIO_XMIDYMID:
311        alignType = "xMidYMid";
312        break;
313    case SVG_PRESERVEASPECTRATIO_XMAXYMID:
314        alignType = "xMaxYMid";
315        break;
316    case SVG_PRESERVEASPECTRATIO_XMINYMAX:
317        alignType = "xMinYMax";
318        break;
319    case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
320        alignType = "xMidYMax";
321        break;
322    case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
323        alignType = "xMaxYMax";
324        break;
325    case SVG_PRESERVEASPECTRATIO_UNKNOWN:
326        alignType = "unknown";
327        break;
328    };
329
330    switch (m_meetOrSlice) {
331    default:
332    case SVG_MEETORSLICE_UNKNOWN:
333        return alignType;
334    case SVG_MEETORSLICE_MEET:
335        return makeString(alignType, " meet");
336    case SVG_MEETORSLICE_SLICE:
337        return makeString(alignType, " slice");
338    };
339}
340
341}
342
343#endif // ENABLE(SVG)
344