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