1/*
2 * Copyright (C) 2012 Adobe Systems Incorporated. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer.
11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following
13 * disclaimer in the documentation and/or other materials
14 * provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
21 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
25 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
26 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include "config.h"
31
32#include "BasicShapes.h"
33
34#include "BasicShapeFunctions.h"
35#include "CalculationValue.h"
36#include "FloatRect.h"
37#include "FloatRoundedRect.h"
38#include "LengthFunctions.h"
39#include "Path.h"
40#include "RenderBox.h"
41#include "SVGPathByteStream.h"
42#include "SVGPathUtilities.h"
43
44#include <wtf/NeverDestroyed.h>
45#include <wtf/TinyLRUCache.h>
46
47namespace WebCore {
48
49void BasicShapeCenterCoordinate::updateComputedLength()
50{
51 if (m_direction == TopLeft) {
52 m_computedLength = m_length.isUndefined() ? Length(0, Fixed) : m_length;
53 return;
54 }
55
56 if (m_length.isUndefined()) {
57 m_computedLength = Length(100, Percent);
58 return;
59 }
60
61 m_computedLength = convertTo100PercentMinusLength(m_length);
62}
63
64struct SVGPathTranslatedByteStream {
65 SVGPathTranslatedByteStream(const FloatPoint& offset, const SVGPathByteStream& rawStream)
66 : m_offset(offset)
67 , m_rawStream(rawStream)
68 { }
69
70 bool operator==(const SVGPathTranslatedByteStream& other) const { return other.m_offset == m_offset && other.m_rawStream == m_rawStream; }
71 bool operator!=(const SVGPathTranslatedByteStream& other) const { return !(*this == other); }
72 bool isEmpty() const { return m_rawStream.isEmpty(); }
73
74 Path path() const
75 {
76 Path path = buildPathFromByteStream(m_rawStream);
77 path.translate(toFloatSize(m_offset));
78 return path;
79 }
80
81 FloatPoint m_offset;
82 SVGPathByteStream m_rawStream;
83};
84
85struct EllipsePathPolicy : public TinyLRUCachePolicy<FloatRect, Path> {
86public:
87 static bool isKeyNull(const FloatRect& rect) { return rect.isEmpty(); }
88
89 static Path createValueForKey(const FloatRect& rect)
90 {
91 Path path;
92 path.addEllipse(rect);
93 return path;
94 }
95};
96
97struct RoundedRectPathPolicy : public TinyLRUCachePolicy<FloatRoundedRect, Path> {
98public:
99 static bool isKeyNull(const FloatRoundedRect& rect) { return rect.isEmpty(); }
100
101 static Path createValueForKey(const FloatRoundedRect& rect)
102 {
103 Path path;
104 path.addRoundedRect(rect);
105 return path;
106 }
107};
108
109struct PolygonPathPolicy : public TinyLRUCachePolicy<Vector<FloatPoint>, Path> {
110public:
111 static bool isKeyNull(const Vector<FloatPoint>& points) { return !points.size(); }
112
113 static Path createValueForKey(const Vector<FloatPoint>& points) { return Path::polygonPathFromPoints(points); }
114};
115
116struct TranslatedByteStreamPathPolicy : public TinyLRUCachePolicy<SVGPathTranslatedByteStream, Path> {
117public:
118 static bool isKeyNull(const SVGPathTranslatedByteStream& stream) { return stream.isEmpty(); }
119
120 static Path createValueForKey(const SVGPathTranslatedByteStream& stream) { return stream.path(); }
121};
122
123static const Path& cachedEllipsePath(const FloatRect& rect)
124{
125 static NeverDestroyed<TinyLRUCache<FloatRect, Path, 4, EllipsePathPolicy>> cache;
126 return cache.get().get(rect);
127}
128
129static const Path& cachedRoundedRectPath(const FloatRoundedRect& rect)
130{
131 static NeverDestroyed<TinyLRUCache<FloatRoundedRect, Path, 4, RoundedRectPathPolicy>> cache;
132 return cache.get().get(rect);
133}
134
135static const Path& cachedPolygonPath(const Vector<FloatPoint>& points)
136{
137 static NeverDestroyed<TinyLRUCache<Vector<FloatPoint>, Path, 4, PolygonPathPolicy>> cache;
138 return cache.get().get(points);
139}
140
141static const Path& cachedTranslatedByteStreamPath(const SVGPathByteStream& stream, const FloatPoint& offset)
142{
143 static NeverDestroyed<TinyLRUCache<SVGPathTranslatedByteStream, Path, 4, TranslatedByteStreamPathPolicy>> cache;
144 return cache.get().get(SVGPathTranslatedByteStream(offset, stream));
145}
146
147bool BasicShapeCircle::operator==(const BasicShape& other) const
148{
149 if (type() != other.type())
150 return false;
151
152 auto& otherCircle = downcast<BasicShapeCircle>(other);
153 return m_centerX == otherCircle.m_centerX
154 && m_centerY == otherCircle.m_centerY
155 && m_radius == otherCircle.m_radius;
156}
157
158float BasicShapeCircle::floatValueForRadiusInBox(float boxWidth, float boxHeight) const
159{
160 if (m_radius.type() == BasicShapeRadius::Value)
161 return floatValueForLength(m_radius.value(), sqrtf((boxWidth * boxWidth + boxHeight * boxHeight) / 2));
162
163 float centerX = floatValueForCenterCoordinate(m_centerX, boxWidth);
164 float centerY = floatValueForCenterCoordinate(m_centerY, boxHeight);
165
166 float widthDelta = std::abs(boxWidth - centerX);
167 float heightDelta = std::abs(boxHeight - centerY);
168 if (m_radius.type() == BasicShapeRadius::ClosestSide)
169 return std::min(std::min(std::abs(centerX), widthDelta), std::min(std::abs(centerY), heightDelta));
170
171 // If radius.type() == BasicShapeRadius::FarthestSide.
172 return std::max(std::max(std::abs(centerX), widthDelta), std::max(std::abs(centerY), heightDelta));
173}
174
175const Path& BasicShapeCircle::path(const FloatRect& boundingBox)
176{
177 float centerX = floatValueForCenterCoordinate(m_centerX, boundingBox.width());
178 float centerY = floatValueForCenterCoordinate(m_centerY, boundingBox.height());
179 float radius = floatValueForRadiusInBox(boundingBox.width(), boundingBox.height());
180
181 return cachedEllipsePath(FloatRect(centerX - radius + boundingBox.x(), centerY - radius + boundingBox.y(), radius * 2, radius * 2));
182}
183
184bool BasicShapeCircle::canBlend(const BasicShape& other) const
185{
186 if (type() != other.type())
187 return false;
188
189 return radius().canBlend(downcast<BasicShapeCircle>(other).radius());
190}
191
192Ref<BasicShape> BasicShapeCircle::blend(const BasicShape& other, double progress) const
193{
194 ASSERT(type() == other.type());
195 auto& otherCircle = downcast<BasicShapeCircle>(other);
196 auto result = BasicShapeCircle::create();
197
198 result->setCenterX(m_centerX.blend(otherCircle.centerX(), progress));
199 result->setCenterY(m_centerY.blend(otherCircle.centerY(), progress));
200 result->setRadius(m_radius.blend(otherCircle.radius(), progress));
201 return result;
202}
203
204bool BasicShapeEllipse::operator==(const BasicShape& other) const
205{
206 if (type() != other.type())
207 return false;
208
209 auto& otherEllipse = downcast<BasicShapeEllipse>(other);
210 return m_centerX == otherEllipse.m_centerX
211 && m_centerY == otherEllipse.m_centerY
212 && m_radiusX == otherEllipse.m_radiusX
213 && m_radiusY == otherEllipse.m_radiusY;
214}
215
216float BasicShapeEllipse::floatValueForRadiusInBox(const BasicShapeRadius& radius, float center, float boxWidthOrHeight) const
217{
218 if (radius.type() == BasicShapeRadius::Value)
219 return floatValueForLength(radius.value(), std::abs(boxWidthOrHeight));
220
221 float widthOrHeightDelta = std::abs(boxWidthOrHeight - center);
222 if (radius.type() == BasicShapeRadius::ClosestSide)
223 return std::min(std::abs(center), widthOrHeightDelta);
224
225 ASSERT(radius.type() == BasicShapeRadius::FarthestSide);
226 return std::max(std::abs(center), widthOrHeightDelta);
227}
228
229const Path& BasicShapeEllipse::path(const FloatRect& boundingBox)
230{
231 float centerX = floatValueForCenterCoordinate(m_centerX, boundingBox.width());
232 float centerY = floatValueForCenterCoordinate(m_centerY, boundingBox.height());
233 float radiusX = floatValueForRadiusInBox(m_radiusX, centerX, boundingBox.width());
234 float radiusY = floatValueForRadiusInBox(m_radiusY, centerY, boundingBox.height());
235
236 return cachedEllipsePath(FloatRect(centerX - radiusX + boundingBox.x(), centerY - radiusY + boundingBox.y(), radiusX * 2, radiusY * 2));
237}
238
239bool BasicShapeEllipse::canBlend(const BasicShape& other) const
240{
241 if (type() != other.type())
242 return false;
243
244 auto& otherEllipse = downcast<BasicShapeEllipse>(other);
245 return radiusX().canBlend(otherEllipse.radiusX()) && radiusY().canBlend(otherEllipse.radiusY());
246}
247
248Ref<BasicShape> BasicShapeEllipse::blend(const BasicShape& other, double progress) const
249{
250 ASSERT(type() == other.type());
251 auto& otherEllipse = downcast<BasicShapeEllipse>(other);
252 auto result = BasicShapeEllipse::create();
253
254 if (m_radiusX.type() != BasicShapeRadius::Value || otherEllipse.radiusX().type() != BasicShapeRadius::Value
255 || m_radiusY.type() != BasicShapeRadius::Value || otherEllipse.radiusY().type() != BasicShapeRadius::Value) {
256 result->setCenterX(otherEllipse.centerX());
257 result->setCenterY(otherEllipse.centerY());
258 result->setRadiusX(otherEllipse.radiusX());
259 result->setRadiusY(otherEllipse.radiusY());
260 return result;
261 }
262
263 result->setCenterX(m_centerX.blend(otherEllipse.centerX(), progress));
264 result->setCenterY(m_centerY.blend(otherEllipse.centerY(), progress));
265 result->setRadiusX(m_radiusX.blend(otherEllipse.radiusX(), progress));
266 result->setRadiusY(m_radiusY.blend(otherEllipse.radiusY(), progress));
267 return result;
268}
269
270bool BasicShapePolygon::operator==(const BasicShape& other) const
271{
272 if (type() != other.type())
273 return false;
274
275 auto& otherPolygon = downcast<BasicShapePolygon>(other);
276 return m_windRule == otherPolygon.m_windRule
277 && m_values == otherPolygon.m_values;
278}
279
280const Path& BasicShapePolygon::path(const FloatRect& boundingBox)
281{
282 ASSERT(!(m_values.size() % 2));
283 size_t length = m_values.size();
284
285 Vector<FloatPoint> points(length / 2);
286 for (size_t i = 0; i < points.size(); ++i) {
287 points[i].setX(floatValueForLength(m_values.at(i * 2), boundingBox.width()) + boundingBox.x());
288 points[i].setY(floatValueForLength(m_values.at(i * 2 + 1), boundingBox.height()) + boundingBox.y());
289 }
290
291 return cachedPolygonPath(points);
292}
293
294bool BasicShapePolygon::canBlend(const BasicShape& other) const
295{
296 if (type() != other.type())
297 return false;
298
299 auto& otherPolygon = downcast<BasicShapePolygon>(other);
300 return values().size() == otherPolygon.values().size() && windRule() == otherPolygon.windRule();
301}
302
303Ref<BasicShape> BasicShapePolygon::blend(const BasicShape& other, double progress) const
304{
305 ASSERT(type() == other.type());
306
307 auto& otherPolygon = downcast<BasicShapePolygon>(other);
308 ASSERT(m_values.size() == otherPolygon.values().size());
309 ASSERT(!(m_values.size() % 2));
310
311 size_t length = m_values.size();
312 auto result = BasicShapePolygon::create();
313 if (!length)
314 return result;
315
316 result->setWindRule(otherPolygon.windRule());
317
318 for (size_t i = 0; i < length; i = i + 2) {
319 result->appendPoint(
320 WebCore::blend(otherPolygon.values().at(i), m_values.at(i), progress),
321 WebCore::blend(otherPolygon.values().at(i + 1), m_values.at(i + 1), progress));
322 }
323
324 return result;
325}
326
327BasicShapePath::BasicShapePath(std::unique_ptr<SVGPathByteStream>&& byteStream)
328 : m_byteStream(WTFMove(byteStream))
329{
330}
331
332const Path& BasicShapePath::path(const FloatRect& boundingBox)
333{
334 return cachedTranslatedByteStreamPath(*m_byteStream, boundingBox.location());
335}
336
337bool BasicShapePath::operator==(const BasicShape& other) const
338{
339 if (type() != other.type())
340 return false;
341
342 auto& otherPath = downcast<BasicShapePath>(other);
343 return m_windRule == otherPath.m_windRule && *m_byteStream == *otherPath.m_byteStream;
344}
345
346bool BasicShapePath::canBlend(const BasicShape& other) const
347{
348 if (type() != other.type())
349 return false;
350
351 auto& otherPath = downcast<BasicShapePath>(other);
352 return windRule() == otherPath.windRule() && canBlendSVGPathByteStreams(*m_byteStream, *otherPath.pathData());
353}
354
355Ref<BasicShape> BasicShapePath::blend(const BasicShape& from, double progress) const
356{
357 ASSERT(type() == from.type());
358
359 auto& fromPath = downcast<BasicShapePath>(from);
360
361 auto resultingPathBytes = std::make_unique<SVGPathByteStream>();
362 buildAnimatedSVGPathByteStream(*fromPath.m_byteStream, *m_byteStream, *resultingPathBytes, progress);
363
364 auto result = BasicShapePath::create(WTFMove(resultingPathBytes));
365 result->setWindRule(windRule());
366 return result;
367}
368
369bool BasicShapeInset::operator==(const BasicShape& other) const
370{
371 if (type() != other.type())
372 return false;
373
374 auto& otherInset = downcast<BasicShapeInset>(other);
375 return m_right == otherInset.m_right
376 && m_top == otherInset.m_top
377 && m_bottom == otherInset.m_bottom
378 && m_left == otherInset.m_left
379 && m_topLeftRadius == otherInset.m_topLeftRadius
380 && m_topRightRadius == otherInset.m_topRightRadius
381 && m_bottomRightRadius == otherInset.m_bottomRightRadius
382 && m_bottomLeftRadius == otherInset.m_bottomLeftRadius;
383}
384
385static FloatSize floatSizeForLengthSize(const LengthSize& lengthSize, const FloatRect& boundingBox)
386{
387 return { floatValueForLength(lengthSize.width, boundingBox.width()),
388 floatValueForLength(lengthSize.height, boundingBox.height()) };
389}
390
391const Path& BasicShapeInset::path(const FloatRect& boundingBox)
392{
393 float left = floatValueForLength(m_left, boundingBox.width());
394 float top = floatValueForLength(m_top, boundingBox.height());
395 auto rect = FloatRect(left + boundingBox.x(), top + boundingBox.y(),
396 std::max<float>(boundingBox.width() - left - floatValueForLength(m_right, boundingBox.width()), 0),
397 std::max<float>(boundingBox.height() - top - floatValueForLength(m_bottom, boundingBox.height()), 0));
398 auto radii = FloatRoundedRect::Radii(floatSizeForLengthSize(m_topLeftRadius, boundingBox),
399 floatSizeForLengthSize(m_topRightRadius, boundingBox),
400 floatSizeForLengthSize(m_bottomLeftRadius, boundingBox),
401 floatSizeForLengthSize(m_bottomRightRadius, boundingBox));
402 radii.scale(calcBorderRadiiConstraintScaleFor(rect, radii));
403
404 return cachedRoundedRectPath(FloatRoundedRect(rect, radii));
405}
406
407bool BasicShapeInset::canBlend(const BasicShape& other) const
408{
409 return type() == other.type();
410}
411
412Ref<BasicShape> BasicShapeInset::blend(const BasicShape& from, double progress) const
413{
414 ASSERT(type() == from.type());
415
416 auto& fromInset = downcast<BasicShapeInset>(from);
417 auto result = BasicShapeInset::create();
418 result->setTop(WebCore::blend(fromInset.top(), top(), progress));
419 result->setRight(WebCore::blend(fromInset.right(), right(), progress));
420 result->setBottom(WebCore::blend(fromInset.bottom(), bottom(), progress));
421 result->setLeft(WebCore::blend(fromInset.left(), left(), progress));
422
423 result->setTopLeftRadius(WebCore::blend(fromInset.topLeftRadius(), topLeftRadius(), progress));
424 result->setTopRightRadius(WebCore::blend(fromInset.topRightRadius(), topRightRadius(), progress));
425 result->setBottomRightRadius(WebCore::blend(fromInset.bottomRightRadius(), bottomRightRadius(), progress));
426 result->setBottomLeftRadius(WebCore::blend(fromInset.bottomLeftRadius(), bottomLeftRadius(), progress));
427
428 return result;
429}
430}
431