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 "third_party/blink/renderer/core/svg/svg_preserve_aspect_ratio.h"
23 
24 #include "third_party/blink/renderer/core/svg/svg_parser_utilities.h"
25 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
26 #include "third_party/blink/renderer/platform/heap/heap.h"
27 #include "third_party/blink/renderer/platform/transforms/affine_transform.h"
28 #include "third_party/blink/renderer/platform/wtf/text/character_visitor.h"
29 #include "third_party/blink/renderer/platform/wtf/text/parsing_utilities.h"
30 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
31 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
32 
33 namespace blink {
34 
SVGPreserveAspectRatio()35 SVGPreserveAspectRatio::SVGPreserveAspectRatio() {
36   SetDefault();
37 }
38 
SetDefault()39 void SVGPreserveAspectRatio::SetDefault() {
40   align_ = kSvgPreserveaspectratioXmidymid;
41   meet_or_slice_ = kSvgMeetorsliceMeet;
42 }
43 
Clone() const44 SVGPreserveAspectRatio* SVGPreserveAspectRatio::Clone() const {
45   auto* preserve_aspect_ratio = MakeGarbageCollected<SVGPreserveAspectRatio>();
46 
47   preserve_aspect_ratio->align_ = align_;
48   preserve_aspect_ratio->meet_or_slice_ = meet_or_slice_;
49 
50   return preserve_aspect_ratio;
51 }
52 
53 template <typename CharType>
ParseInternal(const CharType * & ptr,const CharType * end,bool validate)54 SVGParsingError SVGPreserveAspectRatio::ParseInternal(const CharType*& ptr,
55                                                       const CharType* end,
56                                                       bool validate) {
57   SVGPreserveAspectRatioType align = kSvgPreserveaspectratioXmidymid;
58   SVGMeetOrSliceType meet_or_slice = kSvgMeetorsliceMeet;
59 
60   SetAlign(align);
61   SetMeetOrSlice(meet_or_slice);
62 
63   const CharType* start = ptr;
64   if (!SkipOptionalSVGSpaces(ptr, end))
65     return SVGParsingError(SVGParseStatus::kExpectedEnumeration, ptr - start);
66 
67   if (*ptr == 'n') {
68     if (!SkipToken(ptr, end, "none"))
69       return SVGParsingError(SVGParseStatus::kExpectedEnumeration, ptr - start);
70     align = kSvgPreserveaspectratioNone;
71     SkipOptionalSVGSpaces(ptr, end);
72   } else if (*ptr == 'x') {
73     if ((end - ptr) < 8)
74       return SVGParsingError(SVGParseStatus::kExpectedEnumeration, ptr - start);
75     if (ptr[1] != 'M' || ptr[4] != 'Y' || ptr[5] != 'M')
76       return SVGParsingError(SVGParseStatus::kExpectedEnumeration, ptr - start);
77     if (ptr[2] == 'i') {
78       if (ptr[3] == 'n') {
79         if (ptr[6] == 'i') {
80           if (ptr[7] == 'n')
81             align = kSvgPreserveaspectratioXminymin;
82           else if (ptr[7] == 'd')
83             align = kSvgPreserveaspectratioXminymid;
84           else
85             return SVGParsingError(SVGParseStatus::kExpectedEnumeration,
86                                    ptr - start);
87         } else if (ptr[6] == 'a' && ptr[7] == 'x') {
88           align = kSvgPreserveaspectratioXminymax;
89         } else {
90           return SVGParsingError(SVGParseStatus::kExpectedEnumeration,
91                                  ptr - start);
92         }
93       } else if (ptr[3] == 'd') {
94         if (ptr[6] == 'i') {
95           if (ptr[7] == 'n')
96             align = kSvgPreserveaspectratioXmidymin;
97           else if (ptr[7] == 'd')
98             align = kSvgPreserveaspectratioXmidymid;
99           else
100             return SVGParsingError(SVGParseStatus::kExpectedEnumeration,
101                                    ptr - start);
102         } else if (ptr[6] == 'a' && ptr[7] == 'x') {
103           align = kSvgPreserveaspectratioXmidymax;
104         } else {
105           return SVGParsingError(SVGParseStatus::kExpectedEnumeration,
106                                  ptr - start);
107         }
108       } else {
109         return SVGParsingError(SVGParseStatus::kExpectedEnumeration,
110                                ptr - start);
111       }
112     } else if (ptr[2] == 'a' && ptr[3] == 'x') {
113       if (ptr[6] == 'i') {
114         if (ptr[7] == 'n')
115           align = kSvgPreserveaspectratioXmaxymin;
116         else if (ptr[7] == 'd')
117           align = kSvgPreserveaspectratioXmaxymid;
118         else
119           return SVGParsingError(SVGParseStatus::kExpectedEnumeration,
120                                  ptr - start);
121       } else if (ptr[6] == 'a' && ptr[7] == 'x') {
122         align = kSvgPreserveaspectratioXmaxymax;
123       } else {
124         return SVGParsingError(SVGParseStatus::kExpectedEnumeration,
125                                ptr - start);
126       }
127     } else {
128       return SVGParsingError(SVGParseStatus::kExpectedEnumeration, ptr - start);
129     }
130     ptr += 8;
131     SkipOptionalSVGSpaces(ptr, end);
132   } else {
133     return SVGParsingError(SVGParseStatus::kExpectedEnumeration, ptr - start);
134   }
135 
136   if (ptr < end) {
137     if (*ptr == 'm') {
138       if (!SkipToken(ptr, end, "meet"))
139         return SVGParsingError(SVGParseStatus::kExpectedEnumeration,
140                                ptr - start);
141       SkipOptionalSVGSpaces(ptr, end);
142     } else if (*ptr == 's') {
143       if (!SkipToken(ptr, end, "slice"))
144         return SVGParsingError(SVGParseStatus::kExpectedEnumeration,
145                                ptr - start);
146       SkipOptionalSVGSpaces(ptr, end);
147       if (align != kSvgPreserveaspectratioNone)
148         meet_or_slice = kSvgMeetorsliceSlice;
149     }
150   }
151 
152   if (end != ptr && validate)
153     return SVGParsingError(SVGParseStatus::kTrailingGarbage, ptr - start);
154 
155   SetAlign(align);
156   SetMeetOrSlice(meet_or_slice);
157 
158   return SVGParseStatus::kNoError;
159 }
160 
SetValueAsString(const String & string)161 SVGParsingError SVGPreserveAspectRatio::SetValueAsString(const String& string) {
162   SetDefault();
163 
164   if (string.IsEmpty())
165     return SVGParseStatus::kNoError;
166 
167   return WTF::VisitCharacters(string, [&](const auto* chars, unsigned length) {
168     return ParseInternal(chars, chars + length, true);
169   });
170 }
171 
Parse(const LChar * & ptr,const LChar * end,bool validate)172 bool SVGPreserveAspectRatio::Parse(const LChar*& ptr,
173                                    const LChar* end,
174                                    bool validate) {
175   return ParseInternal(ptr, end, validate) == SVGParseStatus::kNoError;
176 }
177 
Parse(const UChar * & ptr,const UChar * end,bool validate)178 bool SVGPreserveAspectRatio::Parse(const UChar*& ptr,
179                                    const UChar* end,
180                                    bool validate) {
181   return ParseInternal(ptr, end, validate) == SVGParseStatus::kNoError;
182 }
183 
TransformRect(FloatRect & dest_rect,FloatRect & src_rect) const184 void SVGPreserveAspectRatio::TransformRect(FloatRect& dest_rect,
185                                            FloatRect& src_rect) const {
186   if (align_ == kSvgPreserveaspectratioNone)
187     return;
188 
189   FloatSize image_size = src_rect.Size();
190   float orig_dest_width = dest_rect.Width();
191   float orig_dest_height = dest_rect.Height();
192   switch (meet_or_slice_) {
193     case SVGPreserveAspectRatio::kSvgMeetorsliceUnknown:
194       break;
195     case SVGPreserveAspectRatio::kSvgMeetorsliceMeet: {
196       float width_to_height_multiplier = src_rect.Height() / src_rect.Width();
197       if (orig_dest_height > orig_dest_width * width_to_height_multiplier) {
198         dest_rect.SetHeight(orig_dest_width * width_to_height_multiplier);
199         switch (align_) {
200           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXminymid:
201           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmidymid:
202           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmaxymid:
203             dest_rect.SetY(dest_rect.Y() + orig_dest_height / 2 -
204                            dest_rect.Height() / 2);
205             break;
206           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXminymax:
207           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmidymax:
208           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmaxymax:
209             dest_rect.SetY(dest_rect.Y() + orig_dest_height -
210                            dest_rect.Height());
211             break;
212           default:
213             break;
214         }
215       }
216       if (orig_dest_width > orig_dest_height / width_to_height_multiplier) {
217         dest_rect.SetWidth(orig_dest_height / width_to_height_multiplier);
218         switch (align_) {
219           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmidymin:
220           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmidymid:
221           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmidymax:
222             dest_rect.SetX(dest_rect.X() + orig_dest_width / 2 -
223                            dest_rect.Width() / 2);
224             break;
225           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmaxymin:
226           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmaxymid:
227           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmaxymax:
228             dest_rect.SetX(dest_rect.X() + orig_dest_width - dest_rect.Width());
229             break;
230           default:
231             break;
232         }
233       }
234       break;
235     }
236     case SVGPreserveAspectRatio::kSvgMeetorsliceSlice: {
237       float width_to_height_multiplier = src_rect.Height() / src_rect.Width();
238       // If the destination height is less than the height of the image we'll be
239       // drawing.
240       if (orig_dest_height < orig_dest_width * width_to_height_multiplier) {
241         float dest_to_src_multiplier = src_rect.Width() / dest_rect.Width();
242         src_rect.SetHeight(dest_rect.Height() * dest_to_src_multiplier);
243         switch (align_) {
244           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXminymid:
245           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmidymid:
246           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmaxymid:
247             src_rect.SetY(src_rect.Y() + image_size.Height() / 2 -
248                           src_rect.Height() / 2);
249             break;
250           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXminymax:
251           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmidymax:
252           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmaxymax:
253             src_rect.SetY(src_rect.Y() + image_size.Height() -
254                           src_rect.Height());
255             break;
256           default:
257             break;
258         }
259       }
260       // If the destination width is less than the width of the image we'll be
261       // drawing.
262       if (orig_dest_width < orig_dest_height / width_to_height_multiplier) {
263         float dest_to_src_multiplier = src_rect.Height() / dest_rect.Height();
264         src_rect.SetWidth(dest_rect.Width() * dest_to_src_multiplier);
265         switch (align_) {
266           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmidymin:
267           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmidymid:
268           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmidymax:
269             src_rect.SetX(src_rect.X() + image_size.Width() / 2 -
270                           src_rect.Width() / 2);
271             break;
272           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmaxymin:
273           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmaxymid:
274           case SVGPreserveAspectRatio::kSvgPreserveaspectratioXmaxymax:
275             src_rect.SetX(src_rect.X() + image_size.Width() - src_rect.Width());
276             break;
277           default:
278             break;
279         }
280       }
281       break;
282     }
283   }
284 }
285 
ComputeTransform(const FloatRect & view_box,const FloatSize & viewport_size) const286 AffineTransform SVGPreserveAspectRatio::ComputeTransform(
287     const FloatRect& view_box,
288     const FloatSize& viewport_size) const {
289   DCHECK(!view_box.IsEmpty());
290   DCHECK(!viewport_size.IsEmpty());
291   DCHECK_NE(align_, kSvgPreserveaspectratioUnknown);
292 
293   double extended_logical_x = view_box.X();
294   double extended_logical_y = view_box.Y();
295   double extended_logical_width = view_box.Width();
296   double extended_logical_height = view_box.Height();
297   double extended_physical_width = viewport_size.Width();
298   double extended_physical_height = viewport_size.Height();
299 
300   AffineTransform transform;
301   if (align_ == kSvgPreserveaspectratioNone) {
302     transform.ScaleNonUniform(
303         extended_physical_width / extended_logical_width,
304         extended_physical_height / extended_logical_height);
305     transform.Translate(-extended_logical_x, -extended_logical_y);
306     return transform;
307   }
308 
309   double logical_ratio = extended_logical_width / extended_logical_height;
310   double physical_ratio = extended_physical_width / extended_physical_height;
311   if ((logical_ratio < physical_ratio &&
312        (meet_or_slice_ == kSvgMeetorsliceMeet)) ||
313       (logical_ratio >= physical_ratio &&
314        (meet_or_slice_ == kSvgMeetorsliceSlice))) {
315     transform.ScaleNonUniform(
316         extended_physical_height / extended_logical_height,
317         extended_physical_height / extended_logical_height);
318 
319     if (align_ == kSvgPreserveaspectratioXminymin ||
320         align_ == kSvgPreserveaspectratioXminymid ||
321         align_ == kSvgPreserveaspectratioXminymax)
322       transform.Translate(-extended_logical_x, -extended_logical_y);
323     else if (align_ == kSvgPreserveaspectratioXmidymin ||
324              align_ == kSvgPreserveaspectratioXmidymid ||
325              align_ == kSvgPreserveaspectratioXmidymax)
326       transform.Translate(-extended_logical_x - (extended_logical_width -
327                                                  extended_physical_width *
328                                                      extended_logical_height /
329                                                      extended_physical_height) /
330                                                     2,
331                           -extended_logical_y);
332     else
333       transform.Translate(-extended_logical_x - (extended_logical_width -
334                                                  extended_physical_width *
335                                                      extended_logical_height /
336                                                      extended_physical_height),
337                           -extended_logical_y);
338 
339     return transform;
340   }
341 
342   transform.ScaleNonUniform(extended_physical_width / extended_logical_width,
343                             extended_physical_width / extended_logical_width);
344 
345   if (align_ == kSvgPreserveaspectratioXminymin ||
346       align_ == kSvgPreserveaspectratioXmidymin ||
347       align_ == kSvgPreserveaspectratioXmaxymin)
348     transform.Translate(-extended_logical_x, -extended_logical_y);
349   else if (align_ == kSvgPreserveaspectratioXminymid ||
350            align_ == kSvgPreserveaspectratioXmidymid ||
351            align_ == kSvgPreserveaspectratioXmaxymid)
352     transform.Translate(-extended_logical_x,
353                         -extended_logical_y -
354                             (extended_logical_height -
355                              extended_physical_height * extended_logical_width /
356                                  extended_physical_width) /
357                                 2);
358   else
359     transform.Translate(-extended_logical_x,
360                         -extended_logical_y -
361                             (extended_logical_height -
362                              extended_physical_height * extended_logical_width /
363                                  extended_physical_width));
364 
365   return transform;
366 }
367 
ValueAsString() const368 String SVGPreserveAspectRatio::ValueAsString() const {
369   StringBuilder builder;
370 
371   const char* align_string = "";
372   switch (align_) {
373     case kSvgPreserveaspectratioNone:
374       align_string = "none";
375       break;
376     case kSvgPreserveaspectratioXminymin:
377       align_string = "xMinYMin";
378       break;
379     case kSvgPreserveaspectratioXmidymin:
380       align_string = "xMidYMin";
381       break;
382     case kSvgPreserveaspectratioXmaxymin:
383       align_string = "xMaxYMin";
384       break;
385     case kSvgPreserveaspectratioXminymid:
386       align_string = "xMinYMid";
387       break;
388     case kSvgPreserveaspectratioXmidymid:
389       align_string = "xMidYMid";
390       break;
391     case kSvgPreserveaspectratioXmaxymid:
392       align_string = "xMaxYMid";
393       break;
394     case kSvgPreserveaspectratioXminymax:
395       align_string = "xMinYMax";
396       break;
397     case kSvgPreserveaspectratioXmidymax:
398       align_string = "xMidYMax";
399       break;
400     case kSvgPreserveaspectratioXmaxymax:
401       align_string = "xMaxYMax";
402       break;
403     case kSvgPreserveaspectratioUnknown:
404       align_string = "unknown";
405       break;
406   }
407   builder.Append(align_string);
408 
409   const char* meet_or_slice_string = "";
410   switch (meet_or_slice_) {
411     default:
412     case kSvgMeetorsliceUnknown:
413       break;
414     case kSvgMeetorsliceMeet:
415       meet_or_slice_string = " meet";
416       break;
417     case kSvgMeetorsliceSlice:
418       meet_or_slice_string = " slice";
419       break;
420   }
421   builder.Append(meet_or_slice_string);
422   return builder.ToString();
423 }
424 
Add(const SVGPropertyBase * other,const SVGElement *)425 void SVGPreserveAspectRatio::Add(const SVGPropertyBase* other,
426                                  const SVGElement*) {
427   NOTREACHED();
428 }
429 
CalculateAnimatedValue(const SMILAnimationEffectParameters &,float percentage,unsigned repeat_count,const SVGPropertyBase * from_value,const SVGPropertyBase * to_value,const SVGPropertyBase *,const SVGElement *)430 void SVGPreserveAspectRatio::CalculateAnimatedValue(
431     const SMILAnimationEffectParameters&,
432     float percentage,
433     unsigned repeat_count,
434     const SVGPropertyBase* from_value,
435     const SVGPropertyBase* to_value,
436     const SVGPropertyBase*,
437     const SVGElement*) {
438   NOTREACHED();
439 }
440 
CalculateDistance(const SVGPropertyBase * to_value,const SVGElement * context_element) const441 float SVGPreserveAspectRatio::CalculateDistance(
442     const SVGPropertyBase* to_value,
443     const SVGElement* context_element) const {
444   // No paced animations for SVGPreserveAspectRatio.
445   return -1;
446 }
447 
448 }  // namespace blink
449