/* * Copyright (C) 2012 Adobe Systems Incorporated. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "config.h" #include "BasicShapes.h" #include "BasicShapeFunctions.h" #include "CalculationValue.h" #include "FloatRect.h" #include "FloatRoundedRect.h" #include "LengthFunctions.h" #include "Path.h" #include "RenderBox.h" #include "SVGPathByteStream.h" #include "SVGPathUtilities.h" #include #include namespace WebCore { void BasicShapeCenterCoordinate::updateComputedLength() { if (m_direction == TopLeft) { m_computedLength = m_length.isUndefined() ? Length(0, Fixed) : m_length; return; } if (m_length.isUndefined()) { m_computedLength = Length(100, Percent); return; } auto lhs = std::make_unique(Length(100, Percent)); auto rhs = std::make_unique(m_length); auto op = std::make_unique(WTFMove(lhs), WTFMove(rhs), CalcSubtract); m_computedLength = Length(CalculationValue::create(WTFMove(op), CalculationRangeAll)); } struct SVGPathTranslatedByteStream { SVGPathTranslatedByteStream(const FloatPoint& offset, const SVGPathByteStream& rawStream) : m_offset(offset) , m_rawStream(rawStream) { } bool operator==(const SVGPathTranslatedByteStream& other) const { return other.m_offset == m_offset && other.m_rawStream == m_rawStream; } bool operator!=(const SVGPathTranslatedByteStream& other) const { return !(*this == other); } bool isEmpty() const { return m_rawStream.isEmpty(); } Path path() const { Path path; buildPathFromByteStream(m_rawStream, path); path.translate(toFloatSize(m_offset)); return path; } FloatPoint m_offset; SVGPathByteStream m_rawStream; }; struct EllipsePathPolicy : public TinyLRUCachePolicy { public: static bool isKeyNull(const FloatRect& rect) { return rect.isEmpty(); } static Path createValueForKey(const FloatRect& rect) { Path path; path.addEllipse(rect); return path; } }; struct RoundedRectPathPolicy : public TinyLRUCachePolicy { public: static bool isKeyNull(const FloatRoundedRect& rect) { return rect.isEmpty(); } static Path createValueForKey(const FloatRoundedRect& rect) { Path path; path.addRoundedRect(rect); return path; } }; struct PolygonPathPolicy : public TinyLRUCachePolicy, Path> { public: static bool isKeyNull(const Vector& points) { return !points.size(); } static Path createValueForKey(const Vector& points) { return Path::polygonPathFromPoints(points); } }; struct TranslatedByteStreamPathPolicy : public TinyLRUCachePolicy { public: static bool isKeyNull(const SVGPathTranslatedByteStream& stream) { return stream.isEmpty(); } static Path createValueForKey(const SVGPathTranslatedByteStream& stream) { return stream.path(); } }; static const Path& cachedEllipsePath(const FloatRect& rect) { static NeverDestroyed> cache; return cache.get().get(rect); } static const Path& cachedRoundedRectPath(const FloatRoundedRect& rect) { static NeverDestroyed> cache; return cache.get().get(rect); } static const Path& cachedPolygonPath(const Vector& points) { static NeverDestroyed, Path, 4, PolygonPathPolicy>> cache; return cache.get().get(points); } static const Path& cachedTranslatedByteStreamPath(const SVGPathByteStream& stream, const FloatPoint& offset) { static NeverDestroyed> cache; return cache.get().get(SVGPathTranslatedByteStream(offset, stream)); } bool BasicShapeCircle::operator==(const BasicShape& other) const { if (type() != other.type()) return false; auto& otherCircle = downcast(other); return m_centerX == otherCircle.m_centerX && m_centerY == otherCircle.m_centerY && m_radius == otherCircle.m_radius; } float BasicShapeCircle::floatValueForRadiusInBox(float boxWidth, float boxHeight) const { if (m_radius.type() == BasicShapeRadius::Value) return floatValueForLength(m_radius.value(), sqrtf((boxWidth * boxWidth + boxHeight * boxHeight) / 2)); float centerX = floatValueForCenterCoordinate(m_centerX, boxWidth); float centerY = floatValueForCenterCoordinate(m_centerY, boxHeight); float widthDelta = std::abs(boxWidth - centerX); float heightDelta = std::abs(boxHeight - centerY); if (m_radius.type() == BasicShapeRadius::ClosestSide) return std::min(std::min(std::abs(centerX), widthDelta), std::min(std::abs(centerY), heightDelta)); // If radius.type() == BasicShapeRadius::FarthestSide. return std::max(std::max(std::abs(centerX), widthDelta), std::max(std::abs(centerY), heightDelta)); } const Path& BasicShapeCircle::path(const FloatRect& boundingBox) { float centerX = floatValueForCenterCoordinate(m_centerX, boundingBox.width()); float centerY = floatValueForCenterCoordinate(m_centerY, boundingBox.height()); float radius = floatValueForRadiusInBox(boundingBox.width(), boundingBox.height()); return cachedEllipsePath(FloatRect(centerX - radius + boundingBox.x(), centerY - radius + boundingBox.y(), radius * 2, radius * 2)); } bool BasicShapeCircle::canBlend(const BasicShape& other) const { if (type() != other.type()) return false; return radius().canBlend(downcast(other).radius()); } Ref BasicShapeCircle::blend(const BasicShape& other, double progress) const { ASSERT(type() == other.type()); auto& otherCircle = downcast(other); auto result = BasicShapeCircle::create(); result->setCenterX(m_centerX.blend(otherCircle.centerX(), progress)); result->setCenterY(m_centerY.blend(otherCircle.centerY(), progress)); result->setRadius(m_radius.blend(otherCircle.radius(), progress)); return WTFMove(result); } bool BasicShapeEllipse::operator==(const BasicShape& other) const { if (type() != other.type()) return false; auto& otherEllipse = downcast(other); return m_centerX == otherEllipse.m_centerX && m_centerY == otherEllipse.m_centerY && m_radiusX == otherEllipse.m_radiusX && m_radiusY == otherEllipse.m_radiusY; } float BasicShapeEllipse::floatValueForRadiusInBox(const BasicShapeRadius& radius, float center, float boxWidthOrHeight) const { if (radius.type() == BasicShapeRadius::Value) return floatValueForLength(radius.value(), std::abs(boxWidthOrHeight)); float widthOrHeightDelta = std::abs(boxWidthOrHeight - center); if (radius.type() == BasicShapeRadius::ClosestSide) return std::min(std::abs(center), widthOrHeightDelta); ASSERT(radius.type() == BasicShapeRadius::FarthestSide); return std::max(std::abs(center), widthOrHeightDelta); } const Path& BasicShapeEllipse::path(const FloatRect& boundingBox) { float centerX = floatValueForCenterCoordinate(m_centerX, boundingBox.width()); float centerY = floatValueForCenterCoordinate(m_centerY, boundingBox.height()); float radiusX = floatValueForRadiusInBox(m_radiusX, centerX, boundingBox.width()); float radiusY = floatValueForRadiusInBox(m_radiusY, centerY, boundingBox.height()); return cachedEllipsePath(FloatRect(centerX - radiusX + boundingBox.x(), centerY - radiusY + boundingBox.y(), radiusX * 2, radiusY * 2)); } bool BasicShapeEllipse::canBlend(const BasicShape& other) const { if (type() != other.type()) return false; auto& otherEllipse = downcast(other); return radiusX().canBlend(otherEllipse.radiusX()) && radiusY().canBlend(otherEllipse.radiusY()); } Ref BasicShapeEllipse::blend(const BasicShape& other, double progress) const { ASSERT(type() == other.type()); auto& otherEllipse = downcast(other); auto result = BasicShapeEllipse::create(); if (m_radiusX.type() != BasicShapeRadius::Value || otherEllipse.radiusX().type() != BasicShapeRadius::Value || m_radiusY.type() != BasicShapeRadius::Value || otherEllipse.radiusY().type() != BasicShapeRadius::Value) { result->setCenterX(otherEllipse.centerX()); result->setCenterY(otherEllipse.centerY()); result->setRadiusX(otherEllipse.radiusX()); result->setRadiusY(otherEllipse.radiusY()); return WTFMove(result); } result->setCenterX(m_centerX.blend(otherEllipse.centerX(), progress)); result->setCenterY(m_centerY.blend(otherEllipse.centerY(), progress)); result->setRadiusX(m_radiusX.blend(otherEllipse.radiusX(), progress)); result->setRadiusY(m_radiusY.blend(otherEllipse.radiusY(), progress)); return WTFMove(result); } bool BasicShapePolygon::operator==(const BasicShape& other) const { if (type() != other.type()) return false; auto& otherPolygon = downcast(other); return m_windRule == otherPolygon.m_windRule && m_values == otherPolygon.m_values; } const Path& BasicShapePolygon::path(const FloatRect& boundingBox) { ASSERT(!(m_values.size() % 2)); size_t length = m_values.size(); Vector points(length / 2); for (size_t i = 0; i < points.size(); ++i) { points[i].setX(floatValueForLength(m_values.at(i * 2), boundingBox.width()) + boundingBox.x()); points[i].setY(floatValueForLength(m_values.at(i * 2 + 1), boundingBox.height()) + boundingBox.y()); } return cachedPolygonPath(points); } bool BasicShapePolygon::canBlend(const BasicShape& other) const { if (type() != other.type()) return false; auto& otherPolygon = downcast(other); return values().size() == otherPolygon.values().size() && windRule() == otherPolygon.windRule(); } Ref BasicShapePolygon::blend(const BasicShape& other, double progress) const { ASSERT(type() == other.type()); auto& otherPolygon = downcast(other); ASSERT(m_values.size() == otherPolygon.values().size()); ASSERT(!(m_values.size() % 2)); size_t length = m_values.size(); auto result = BasicShapePolygon::create(); if (!length) return WTFMove(result); result->setWindRule(otherPolygon.windRule()); for (size_t i = 0; i < length; i = i + 2) { result->appendPoint(m_values.at(i).blend(otherPolygon.values().at(i), progress), m_values.at(i + 1).blend(otherPolygon.values().at(i + 1), progress)); } return WTFMove(result); } BasicShapePath::BasicShapePath(std::unique_ptr&& byteStream) : m_byteStream(WTFMove(byteStream)) { } const Path& BasicShapePath::path(const FloatRect& boundingBox) { return cachedTranslatedByteStreamPath(*m_byteStream, boundingBox.location()); } bool BasicShapePath::operator==(const BasicShape& other) const { if (type() != other.type()) return false; auto& otherPath = downcast(other); return m_windRule == otherPath.m_windRule && *m_byteStream == *otherPath.m_byteStream; } bool BasicShapePath::canBlend(const BasicShape& other) const { if (type() != other.type()) return false; auto& otherPath = downcast(other); return windRule() == otherPath.windRule() && canBlendSVGPathByteStreams(*m_byteStream, *otherPath.pathData()); } Ref BasicShapePath::blend(const BasicShape& from, double progress) const { ASSERT(type() == from.type()); auto& fromPath = downcast(from); auto resultingPathBytes = std::make_unique(); buildAnimatedSVGPathByteStream(*fromPath.m_byteStream, *m_byteStream, *resultingPathBytes, progress); auto result = BasicShapePath::create(WTFMove(resultingPathBytes)); result->setWindRule(windRule()); return WTFMove(result); } bool BasicShapeInset::operator==(const BasicShape& other) const { if (type() != other.type()) return false; auto& otherInset = downcast(other); return m_right == otherInset.m_right && m_top == otherInset.m_top && m_bottom == otherInset.m_bottom && m_left == otherInset.m_left && m_topLeftRadius == otherInset.m_topLeftRadius && m_topRightRadius == otherInset.m_topRightRadius && m_bottomRightRadius == otherInset.m_bottomRightRadius && m_bottomLeftRadius == otherInset.m_bottomLeftRadius; } static FloatSize floatSizeForLengthSize(const LengthSize& lengthSize, const FloatRect& boundingBox) { return FloatSize(floatValueForLength(lengthSize.width(), boundingBox.width()), floatValueForLength(lengthSize.height(), boundingBox.height())); } const Path& BasicShapeInset::path(const FloatRect& boundingBox) { float left = floatValueForLength(m_left, boundingBox.width()); float top = floatValueForLength(m_top, boundingBox.height()); auto rect = FloatRect(left + boundingBox.x(), top + boundingBox.y(), std::max(boundingBox.width() - left - floatValueForLength(m_right, boundingBox.width()), 0), std::max(boundingBox.height() - top - floatValueForLength(m_bottom, boundingBox.height()), 0)); auto radii = FloatRoundedRect::Radii(floatSizeForLengthSize(m_topLeftRadius, boundingBox), floatSizeForLengthSize(m_topRightRadius, boundingBox), floatSizeForLengthSize(m_bottomLeftRadius, boundingBox), floatSizeForLengthSize(m_bottomRightRadius, boundingBox)); radii.scale(calcBorderRadiiConstraintScaleFor(rect, radii)); return cachedRoundedRectPath(FloatRoundedRect(rect, radii)); } bool BasicShapeInset::canBlend(const BasicShape& other) const { return type() == other.type(); } Ref BasicShapeInset::blend(const BasicShape& other, double progress) const { ASSERT(type() == other.type()); auto& otherInset = downcast(other); auto result = BasicShapeInset::create(); result->setTop(m_top.blend(otherInset.top(), progress)); result->setRight(m_right.blend(otherInset.right(), progress)); result->setBottom(m_bottom.blend(otherInset.bottom(), progress)); result->setLeft(m_left.blend(otherInset.left(), progress)); result->setTopLeftRadius(m_topLeftRadius.blend(otherInset.topLeftRadius(), progress)); result->setTopRightRadius(m_topRightRadius.blend(otherInset.topRightRadius(), progress)); result->setBottomRightRadius(m_bottomRightRadius.blend(otherInset.bottomRightRadius(), progress)); result->setBottomLeftRadius(m_bottomLeftRadius.blend(otherInset.bottomLeftRadius(), progress)); return WTFMove(result); } }