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 | |
47 | namespace WebCore { |
48 | |
49 | void 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 | |
64 | struct 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 | |
85 | struct EllipsePathPolicy : public TinyLRUCachePolicy<FloatRect, Path> { |
86 | public: |
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 | |
97 | struct RoundedRectPathPolicy : public TinyLRUCachePolicy<FloatRoundedRect, Path> { |
98 | public: |
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 | |
109 | struct PolygonPathPolicy : public TinyLRUCachePolicy<Vector<FloatPoint>, Path> { |
110 | public: |
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 | |
116 | struct TranslatedByteStreamPathPolicy : public TinyLRUCachePolicy<SVGPathTranslatedByteStream, Path> { |
117 | public: |
118 | static bool isKeyNull(const SVGPathTranslatedByteStream& stream) { return stream.isEmpty(); } |
119 | |
120 | static Path createValueForKey(const SVGPathTranslatedByteStream& stream) { return stream.path(); } |
121 | }; |
122 | |
123 | static const Path& cachedEllipsePath(const FloatRect& rect) |
124 | { |
125 | static NeverDestroyed<TinyLRUCache<FloatRect, Path, 4, EllipsePathPolicy>> cache; |
126 | return cache.get().get(rect); |
127 | } |
128 | |
129 | static const Path& cachedRoundedRectPath(const FloatRoundedRect& rect) |
130 | { |
131 | static NeverDestroyed<TinyLRUCache<FloatRoundedRect, Path, 4, RoundedRectPathPolicy>> cache; |
132 | return cache.get().get(rect); |
133 | } |
134 | |
135 | static 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 | |
141 | static 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 | |
147 | bool 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 | |
158 | float 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 | |
175 | const 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 | |
184 | bool 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 | |
192 | Ref<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 | |
204 | bool 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 | |
216 | float 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 | |
229 | const 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 | |
239 | bool 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 | |
248 | Ref<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 | |
270 | bool 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 | |
280 | const 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 | |
294 | bool 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 | |
303 | Ref<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 | |
327 | BasicShapePath::BasicShapePath(std::unique_ptr<SVGPathByteStream>&& byteStream) |
328 | : m_byteStream(WTFMove(byteStream)) |
329 | { |
330 | } |
331 | |
332 | const Path& BasicShapePath::path(const FloatRect& boundingBox) |
333 | { |
334 | return cachedTranslatedByteStreamPath(*m_byteStream, boundingBox.location()); |
335 | } |
336 | |
337 | bool 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 | |
346 | bool 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 | |
355 | Ref<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 | |
369 | bool 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 | |
385 | static FloatSize floatSizeForLengthSize(const LengthSize& lengthSize, const FloatRect& boundingBox) |
386 | { |
387 | return { floatValueForLength(lengthSize.width, boundingBox.width()), |
388 | floatValueForLength(lengthSize.height, boundingBox.height()) }; |
389 | } |
390 | |
391 | const 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 | |
407 | bool BasicShapeInset::canBlend(const BasicShape& other) const |
408 | { |
409 | return type() == other.type(); |
410 | } |
411 | |
412 | Ref<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 | |