/* * Copyright (C) 2014-2015 Apple Inc. 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 APPLE INC. ``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 APPLE INC. OR * CONTRIBUTORS 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 "PathUtilities.h" #include "AffineTransform.h" #include "FloatPoint.h" #include "FloatRect.h" #include "GeometryUtilities.h" #include #include namespace WebCore { class FloatPointGraph { WTF_MAKE_NONCOPYABLE(FloatPointGraph); public: FloatPointGraph() { } class Node : public FloatPoint { WTF_MAKE_NONCOPYABLE(Node); public: Node(FloatPoint point) : FloatPoint(point) { } const Vector& nextPoints() const { return m_nextPoints; } void addNextPoint(Node* node) { if (!m_nextPoints.contains(node)) m_nextPoints.append(node); } bool isVisited() const { return m_visited; } void visit() { m_visited = true; } void reset() { m_visited = false; m_nextPoints.clear(); } private: Vector m_nextPoints; bool m_visited { false }; }; typedef std::pair Edge; typedef Vector Polygon; Node* findOrCreateNode(FloatPoint); void reset() { for (auto& node : m_allNodes) node->reset(); } private: Vector> m_allNodes; }; FloatPointGraph::Node* FloatPointGraph::findOrCreateNode(FloatPoint point) { for (auto& testNode : m_allNodes) { if (areEssentiallyEqual(*testNode, point)) return testNode.get(); } m_allNodes.append(std::make_unique(point)); return m_allNodes.last().get(); } static bool findLineSegmentIntersection(const FloatPointGraph::Edge& edgeA, const FloatPointGraph::Edge& edgeB, FloatPoint& intersectionPoint) { if (!findIntersection(*edgeA.first, *edgeA.second, *edgeB.first, *edgeB.second, intersectionPoint)) return false; FloatPoint edgeAVec(*edgeA.second - *edgeA.first); FloatPoint edgeBVec(*edgeB.second - *edgeB.first); float dotA = edgeAVec.dot(toFloatPoint(intersectionPoint - *edgeA.first)); if (dotA < 0 || dotA > edgeAVec.lengthSquared()) return false; float dotB = edgeBVec.dot(toFloatPoint(intersectionPoint - *edgeB.first)); if (dotB < 0 || dotB > edgeBVec.lengthSquared()) return false; return true; } static bool addIntersectionPoints(Vector& polys, FloatPointGraph& graph) { bool foundAnyIntersections = false; Vector allEdges; for (auto& poly : polys) allEdges.appendVector(poly); for (const FloatPointGraph::Edge& edgeA : allEdges) { Vector intersectionPoints({edgeA.first, edgeA.second}); for (const FloatPointGraph::Edge& edgeB : allEdges) { if (&edgeA == &edgeB) continue; FloatPoint intersectionPoint; if (!findLineSegmentIntersection(edgeA, edgeB, intersectionPoint)) continue; foundAnyIntersections = true; intersectionPoints.append(graph.findOrCreateNode(intersectionPoint)); } std::sort(intersectionPoints.begin(), intersectionPoints.end(), [edgeA] (FloatPointGraph::Node* a, FloatPointGraph::Node* b) { return FloatPoint(*edgeA.first - *b).lengthSquared() > FloatPoint(*edgeA.first - *a).lengthSquared(); }); for (unsigned pointIndex = 1; pointIndex < intersectionPoints.size(); pointIndex++) intersectionPoints[pointIndex - 1]->addNextPoint(intersectionPoints[pointIndex]); } return foundAnyIntersections; } static FloatPointGraph::Polygon walkGraphAndExtractPolygon(FloatPointGraph::Node* startNode) { FloatPointGraph::Polygon outPoly; FloatPointGraph::Node* currentNode = startNode; FloatPointGraph::Node* previousNode = startNode; do { currentNode->visit(); FloatPoint currentVec(*previousNode - *currentNode); currentVec.normalize(); // Walk the graph, at each node choosing the next non-visited // point with the greatest internal angle. FloatPointGraph::Node* nextNode = nullptr; float nextNodeAngle = 0; for (auto* potentialNextNode : currentNode->nextPoints()) { if (potentialNextNode == currentNode) continue; // If we can get back to the start, we should, ignoring the fact that we already visited it. // Otherwise we'll head inside the shape. if (potentialNextNode == startNode) { nextNode = startNode; break; } if (potentialNextNode->isVisited()) continue; FloatPoint nextVec(*potentialNextNode - *currentNode); nextVec.normalize(); float angle = acos(nextVec.dot(currentVec)); float crossZ = nextVec.x() * currentVec.y() - nextVec.y() * currentVec.x(); if (crossZ < 0) angle = (2 * piFloat) - angle; if (!nextNode || angle > nextNodeAngle) { nextNode = potentialNextNode; nextNodeAngle = angle; } } // If we don't end up at a node adjacent to the starting node, // something went wrong (there's probably a hole in the shape), // so bail out. We'll use a bounding box instead. if (!nextNode) return FloatPointGraph::Polygon(); outPoly.append(std::make_pair(currentNode, nextNode)); previousNode = currentNode; currentNode = nextNode; } while (currentNode != startNode); return outPoly; } static FloatPointGraph::Node* findUnvisitedPolygonStartPoint(Vector& polys) { for (auto& poly : polys) { for (auto& edge : poly) { if (edge.first->isVisited() || edge.second->isVisited()) goto nextPolygon; } // FIXME: We should make sure we find an outside edge to start with. return poly[0].first; nextPolygon: continue; } return nullptr; } static Vector unitePolygons(Vector& polys, FloatPointGraph& graph) { graph.reset(); // There are no intersections, so the polygons are disjoint (we already removed wholly-contained rects in an earlier step). if (!addIntersectionPoints(polys, graph)) return polys; Vector unitedPolygons; while (FloatPointGraph::Node* startNode = findUnvisitedPolygonStartPoint(polys)) { FloatPointGraph::Polygon unitedPolygon = walkGraphAndExtractPolygon(startNode); if (unitedPolygon.isEmpty()) return Vector(); unitedPolygons.append(unitedPolygon); } return unitedPolygons; } static FloatPointGraph::Polygon edgesForRect(FloatRect rect, FloatPointGraph& graph) { auto minMin = graph.findOrCreateNode(rect.minXMinYCorner()); auto minMax = graph.findOrCreateNode(rect.minXMaxYCorner()); auto maxMax = graph.findOrCreateNode(rect.maxXMaxYCorner()); auto maxMin = graph.findOrCreateNode(rect.maxXMinYCorner()); return FloatPointGraph::Polygon({ std::make_pair(minMin, maxMin), std::make_pair(maxMin, maxMax), std::make_pair(maxMax, minMax), std::make_pair(minMax, minMin) }); } Vector PathUtilities::pathsWithShrinkWrappedRects(const Vector& rects, float radius) { Vector paths; if (rects.isEmpty()) return paths; if (rects.size() > 20) { Path path; path.addRoundedRect(unionRect(rects), FloatSize(radius, radius)); paths.append(path); return paths; } Vector sortedRects = rects; std::sort(sortedRects.begin(), sortedRects.end(), [](FloatRect a, FloatRect b) { return b.y() > a.y(); }); FloatPointGraph graph; Vector rectPolygons; rectPolygons.reserveInitialCapacity(sortedRects.size()); for (auto& rect : sortedRects) { bool isContained = false; for (auto& otherRect : sortedRects) { if (&rect == &otherRect) continue; if (otherRect.contains(rect)) { isContained = true; break; } } if (!isContained) rectPolygons.append(edgesForRect(rect, graph)); } Vector polys = unitePolygons(rectPolygons, graph); if (polys.isEmpty()) { Path path; path.addRoundedRect(unionRect(sortedRects), FloatSize(radius, radius)); paths.append(path); return paths; } for (auto& poly : polys) { Path path; for (unsigned i = 0; i < poly.size(); i++) { FloatPointGraph::Edge& toEdge = poly[i]; // Connect the first edge to the last. FloatPointGraph::Edge& fromEdge = (i > 0) ? poly[i - 1] : poly[poly.size() - 1]; FloatPoint fromEdgeVec = toFloatPoint(*fromEdge.second - *fromEdge.first); FloatPoint toEdgeVec = toFloatPoint(*toEdge.second - *toEdge.first); // Clamp the radius to no more than half the length of either adjacent edge, // because we want a smooth curve and don't want unequal radii. float clampedRadius = std::min(radius, fabsf(fromEdgeVec.x() ? fromEdgeVec.x() : fromEdgeVec.y()) / 2); clampedRadius = std::min(clampedRadius, fabsf(toEdgeVec.x() ? toEdgeVec.x() : toEdgeVec.y()) / 2); FloatPoint fromEdgeNorm = fromEdgeVec; fromEdgeNorm.normalize(); FloatPoint toEdgeNorm = toEdgeVec; toEdgeNorm.normalize(); // Project the radius along the incoming and outgoing edge. FloatSize fromOffset = clampedRadius * toFloatSize(fromEdgeNorm); FloatSize toOffset = clampedRadius * toFloatSize(toEdgeNorm); if (!i) path.moveTo(*fromEdge.second - fromOffset); else path.addLineTo(*fromEdge.second - fromOffset); path.addArcTo(*fromEdge.second, *toEdge.first + toOffset, clampedRadius); } path.closeSubpath(); paths.append(path); } return paths; } Path PathUtilities::pathWithShrinkWrappedRects(const Vector& rects, float radius) { Vector paths = pathsWithShrinkWrappedRects(rects, radius); Path unionPath; for (const auto& path : paths) unionPath.addPath(path, AffineTransform()); return unionPath; } }