| /* |
| * 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 "BorderData.h" |
| #include "FloatPoint.h" |
| #include "FloatRect.h" |
| #include "FloatRoundedRect.h" |
| #include "GeometryUtilities.h" |
| #include <math.h> |
| #include <wtf/MathExtras.h> |
| |
| 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<Node*>& 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<Node*> m_nextPoints; |
| bool m_visited { false }; |
| }; |
| |
| typedef std::pair<Node*, Node*> Edge; |
| typedef Vector<Edge> Polygon; |
| |
| Node* findOrCreateNode(FloatPoint); |
| |
| void reset() |
| { |
| for (auto& node : m_allNodes) |
| node->reset(); |
| } |
| |
| private: |
| Vector<std::unique_ptr<Node>> m_allNodes; |
| }; |
| |
| FloatPointGraph::Node* FloatPointGraph::findOrCreateNode(FloatPoint point) |
| { |
| for (auto& testNode : m_allNodes) { |
| if (areEssentiallyEqual(*testNode, point)) |
| return testNode.get(); |
| } |
| |
| m_allNodes.append(makeUnique<FloatPointGraph::Node>(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<FloatPointGraph::Polygon>& polys, FloatPointGraph& graph) |
| { |
| bool foundAnyIntersections = false; |
| |
| Vector<FloatPointGraph::Edge> allEdges; |
| for (auto& poly : polys) |
| allEdges.appendVector(poly); |
| |
| for (const FloatPointGraph::Edge& edgeA : allEdges) { |
| Vector<FloatPointGraph::Node*> 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](auto* a, auto* 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<FloatPointGraph::Polygon>& 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<FloatPointGraph::Polygon> unitePolygons(Vector<FloatPointGraph::Polygon>& 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<FloatPointGraph::Polygon> unitedPolygons; |
| |
| while (FloatPointGraph::Node* startNode = findUnvisitedPolygonStartPoint(polys)) { |
| FloatPointGraph::Polygon unitedPolygon = walkGraphAndExtractPolygon(startNode); |
| if (unitedPolygon.isEmpty()) |
| return Vector<FloatPointGraph::Polygon>(); |
| 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) |
| }); |
| } |
| |
| static Vector<FloatPointGraph::Polygon> polygonsForRect(const Vector<FloatRect>& rects, FloatPointGraph& graph) |
| { |
| Vector<FloatRect> sortedRects = rects; |
| // FIXME: Replace it with 2 dimensional sort. |
| std::sort(sortedRects.begin(), sortedRects.end(), [](FloatRect a, FloatRect b) { |
| return a.x() < b.x(); |
| }); |
| std::sort(sortedRects.begin(), sortedRects.end(), [](FloatRect a, FloatRect b) { |
| return a.y() < b.y(); |
| }); |
| |
| Vector<FloatPointGraph::Polygon> 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.uncheckedAppend(edgesForRect(rect, graph)); |
| } |
| return unitePolygons(rectPolygons, graph); |
| } |
| |
| Vector<Path> PathUtilities::pathsWithShrinkWrappedRects(const Vector<FloatRect>& rects, float radius) |
| { |
| Vector<Path> paths; |
| |
| if (rects.isEmpty()) |
| return paths; |
| |
| if (rects.size() > 20) { |
| Path path; |
| for (const auto& rect : rects) |
| path.addRoundedRect(rect, FloatSize(radius, radius)); |
| paths.append(path); |
| return paths; |
| } |
| |
| FloatPointGraph graph; |
| Vector<FloatPointGraph::Polygon> polys = polygonsForRect(rects, graph); |
| if (polys.isEmpty()) { |
| Path path; |
| for (const auto& rect : rects) |
| path.addRoundedRect(rect, 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<FloatRect>& rects, float radius) |
| { |
| Vector<Path> paths = pathsWithShrinkWrappedRects(rects, radius); |
| |
| Path unionPath; |
| for (const auto& path : paths) |
| unionPath.addPath(path, AffineTransform()); |
| |
| return unionPath; |
| } |
| |
| static std::pair<FloatPoint, FloatPoint> startAndEndPointsForCorner(const FloatPointGraph::Edge& fromEdge, const FloatPointGraph::Edge& toEdge, const FloatSize& radius) |
| { |
| FloatPoint startPoint; |
| FloatPoint endPoint; |
| |
| FloatSize fromEdgeVector = *fromEdge.second - *fromEdge.first; |
| FloatSize toEdgeVector = *toEdge.second - *toEdge.first; |
| |
| FloatPoint fromEdgeNorm = toFloatPoint(fromEdgeVector); |
| fromEdgeNorm.normalize(); |
| FloatSize fromOffset = FloatSize(radius.width() * fromEdgeNorm.x(), radius.height() * fromEdgeNorm.y()); |
| startPoint = *fromEdge.second - fromOffset; |
| |
| FloatPoint toEdgeNorm = toFloatPoint(toEdgeVector); |
| toEdgeNorm.normalize(); |
| FloatSize toOffset = FloatSize(radius.width() * toEdgeNorm.x(), radius.height() * toEdgeNorm.y()); |
| endPoint = *toEdge.first + toOffset; |
| return std::make_pair(startPoint, endPoint); |
| } |
| |
| enum class CornerType { TopLeft, TopRight, BottomRight, BottomLeft, Other }; |
| static CornerType cornerType(const FloatPointGraph::Edge& fromEdge, const FloatPointGraph::Edge& toEdge) |
| { |
| auto fromEdgeVector = *fromEdge.second - *fromEdge.first; |
| auto toEdgeVector = *toEdge.second - *toEdge.first; |
| |
| if (fromEdgeVector.height() < 0 && toEdgeVector.width() > 0) |
| return CornerType::TopLeft; |
| if (fromEdgeVector.width() > 0 && toEdgeVector.height() > 0) |
| return CornerType::TopRight; |
| if (fromEdgeVector.height() > 0 && toEdgeVector.width() < 0) |
| return CornerType::BottomRight; |
| if (fromEdgeVector.width() < 0 && toEdgeVector.height() < 0) |
| return CornerType::BottomLeft; |
| return CornerType::Other; |
| } |
| |
| static CornerType cornerTypeForMultiline(const FloatPointGraph::Edge& fromEdge, const FloatPointGraph::Edge& toEdge, const Vector<FloatPoint>& corners) |
| { |
| auto corner = cornerType(fromEdge, toEdge); |
| if (corner == CornerType::TopLeft && corners.at(0) == *fromEdge.second) |
| return corner; |
| if (corner == CornerType::TopRight && corners.at(1) == *fromEdge.second) |
| return corner; |
| if (corner == CornerType::BottomRight && corners.at(2) == *fromEdge.second) |
| return corner; |
| if (corner == CornerType::BottomLeft && corners.at(3) == *fromEdge.second) |
| return corner; |
| return CornerType::Other; |
| } |
| |
| static std::pair<FloatPoint, FloatPoint> controlPointsForBezierCurve(CornerType cornerType, const FloatPointGraph::Edge& fromEdge, |
| const FloatPointGraph::Edge& toEdge, const FloatSize& radius) |
| { |
| FloatPoint cp1; |
| FloatPoint cp2; |
| switch (cornerType) { |
| case CornerType::TopLeft: { |
| cp1 = FloatPoint(fromEdge.second->x(), fromEdge.second->y() + radius.height() * Path::circleControlPoint()); |
| cp2 = FloatPoint(toEdge.first->x() + radius.width() * Path::circleControlPoint(), toEdge.first->y()); |
| break; |
| } |
| case CornerType::TopRight: { |
| cp1 = FloatPoint(fromEdge.second->x() - radius.width() * Path::circleControlPoint(), fromEdge.second->y()); |
| cp2 = FloatPoint(toEdge.first->x(), toEdge.first->y() + radius.height() * Path::circleControlPoint()); |
| break; |
| } |
| case CornerType::BottomRight: { |
| cp1 = FloatPoint(fromEdge.second->x(), fromEdge.second->y() - radius.height() * Path::circleControlPoint()); |
| cp2 = FloatPoint(toEdge.first->x() - radius.width() * Path::circleControlPoint(), toEdge.first->y()); |
| break; |
| } |
| case CornerType::BottomLeft: { |
| cp1 = FloatPoint(fromEdge.second->x() + radius.width() * Path::circleControlPoint(), fromEdge.second->y()); |
| cp2 = FloatPoint(toEdge.first->x(), toEdge.first->y() - radius.height() * Path::circleControlPoint()); |
| break; |
| } |
| case CornerType::Other: { |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| } |
| return std::make_pair(cp1, cp2); |
| } |
| |
| static FloatRoundedRect::Radii adjustedtRadiiForHuggingCurve(const FloatSize& topLeftRadius, const FloatSize& topRightRadius, |
| const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius, float outlineOffset) |
| { |
| FloatRoundedRect::Radii radii; |
| // This adjusts the radius so that it follows the border curve even when offset is present. |
| auto adjustedRadius = [outlineOffset](const FloatSize& radius) |
| { |
| FloatSize expandSize; |
| if (radius.width() > outlineOffset) |
| expandSize.setWidth(std::min(outlineOffset, radius.width() - outlineOffset)); |
| if (radius.height() > outlineOffset) |
| expandSize.setHeight(std::min(outlineOffset, radius.height() - outlineOffset)); |
| FloatSize adjustedRadius = radius; |
| adjustedRadius.expand(expandSize.width(), expandSize.height()); |
| // Do not go to negative radius. |
| return adjustedRadius.expandedTo(FloatSize(0, 0)); |
| }; |
| |
| radii.setTopLeft(adjustedRadius(topLeftRadius)); |
| radii.setTopRight(adjustedRadius(topRightRadius)); |
| radii.setBottomRight(adjustedRadius(bottomRightRadius)); |
| radii.setBottomLeft(adjustedRadius(bottomLeftRadius)); |
| return radii; |
| } |
| |
| static std::optional<FloatRect> rectFromPolygon(const FloatPointGraph::Polygon& poly) |
| { |
| if (poly.size() != 4) |
| return std::optional<FloatRect>(); |
| |
| std::optional<FloatPoint> topLeft; |
| std::optional<FloatPoint> bottomRight; |
| for (unsigned i = 0; i < poly.size(); ++i) { |
| const auto& toEdge = poly[i]; |
| const auto& fromEdge = (i > 0) ? poly[i - 1] : poly[poly.size() - 1]; |
| auto corner = cornerType(fromEdge, toEdge); |
| if (corner == CornerType::TopLeft) { |
| ASSERT(!topLeft); |
| topLeft = *fromEdge.second; |
| } else if (corner == CornerType::BottomRight) { |
| ASSERT(!bottomRight); |
| bottomRight = *fromEdge.second; |
| } |
| } |
| if (!topLeft || !bottomRight) |
| return std::optional<FloatRect>(); |
| return FloatRect(topLeft.value(), bottomRight.value()); |
| } |
| |
| Path PathUtilities::pathWithShrinkWrappedRectsForOutline(const Vector<FloatRect>& rects, const BorderData& borderData, |
| float outlineOffset, TextDirection direction, WritingMode writingMode, float deviceScaleFactor) |
| { |
| ASSERT(borderData.hasBorderRadius()); |
| |
| FloatSize topLeftRadius { borderData.topLeftRadius().width.value(), borderData.topLeftRadius().height.value() }; |
| FloatSize topRightRadius { borderData.topRightRadius().width.value(), borderData.topRightRadius().height.value() }; |
| FloatSize bottomRightRadius { borderData.bottomRightRadius().width.value(), borderData.bottomRightRadius().height.value() }; |
| FloatSize bottomLeftRadius { borderData.bottomLeftRadius().width.value(), borderData.bottomLeftRadius().height.value() }; |
| |
| auto roundedRect = [topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius, outlineOffset, deviceScaleFactor] (const FloatRect& rect) |
| { |
| auto radii = adjustedtRadiiForHuggingCurve(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, outlineOffset); |
| radii.scale(calcBorderRadiiConstraintScaleFor(rect, radii)); |
| RoundedRect roundedRect(LayoutRect(rect), |
| RoundedRect::Radii(LayoutSize(radii.topLeft()), LayoutSize(radii.topRight()), LayoutSize(radii.bottomLeft()), LayoutSize(radii.bottomRight()))); |
| Path path; |
| path.addRoundedRect(roundedRect.pixelSnappedRoundedRectForPainting(deviceScaleFactor)); |
| return path; |
| }; |
| |
| if (rects.size() == 1) |
| return roundedRect(rects.at(0)); |
| |
| FloatPointGraph graph; |
| const auto polys = polygonsForRect(rects, graph); |
| // Fall back to corner painting with no radius for empty and disjoint rectangles. |
| if (!polys.size() || polys.size() > 1) |
| return Path(); |
| const auto& poly = polys.at(0); |
| // Fast path when poly has one rect only. |
| std::optional<FloatRect> rect = rectFromPolygon(poly); |
| if (rect) |
| return roundedRect(rect.value()); |
| |
| Path path; |
| // Multiline outline needs to match multiline border painting. Only first and last lines are getting rounded borders. |
| auto isLeftToRight = isLeftToRightDirection(direction); |
| auto firstLineRect = isLeftToRight ? rects.at(0) : rects.at(rects.size() - 1); |
| auto lastLineRect = isLeftToRight ? rects.at(rects.size() - 1) : rects.at(0); |
| // Adjust radius so that it matches the box border. |
| auto firstLineRadii = FloatRoundedRect::Radii(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); |
| auto lastLineRadii = FloatRoundedRect::Radii(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); |
| firstLineRadii.scale(calcBorderRadiiConstraintScaleFor(firstLineRect, firstLineRadii)); |
| lastLineRadii.scale(calcBorderRadiiConstraintScaleFor(lastLineRect, lastLineRadii)); |
| topLeftRadius = firstLineRadii.topLeft(); |
| bottomLeftRadius = firstLineRadii.bottomLeft(); |
| topRightRadius = lastLineRadii.topRight(); |
| bottomRightRadius = lastLineRadii.bottomRight(); |
| Vector<FloatPoint> corners; |
| // physical topLeft/topRight/bottomRight/bottomLeft |
| auto isHorizontal = isHorizontalWritingMode(writingMode); |
| corners.append(firstLineRect.minXMinYCorner()); |
| corners.append(isHorizontal ? lastLineRect.maxXMinYCorner() : firstLineRect.maxXMinYCorner()); |
| corners.append(lastLineRect.maxXMaxYCorner()); |
| corners.append(isHorizontal ? firstLineRect.minXMaxYCorner() : lastLineRect.minXMaxYCorner()); |
| |
| for (unsigned i = 0; i < poly.size(); ++i) { |
| auto moveOrAddLineTo = [i, &path] (const FloatPoint& startPoint) |
| { |
| if (!i) |
| path.moveTo(startPoint); |
| else |
| path.addLineTo(startPoint); |
| }; |
| const auto& toEdge = poly[i]; |
| const auto& fromEdge = (i > 0) ? poly[i - 1] : poly[poly.size() - 1]; |
| FloatSize radius; |
| auto corner = cornerTypeForMultiline(fromEdge, toEdge, corners); |
| switch (corner) { |
| case CornerType::TopLeft: { |
| radius = topLeftRadius; |
| break; |
| } |
| case CornerType::TopRight: { |
| radius = topRightRadius; |
| break; |
| } |
| case CornerType::BottomRight: { |
| radius = bottomRightRadius; |
| break; |
| } |
| case CornerType::BottomLeft: { |
| radius = bottomLeftRadius; |
| break; |
| } |
| case CornerType::Other: { |
| // Do not apply border radius on corners that normal border painting skips. (multiline content) |
| moveOrAddLineTo(*fromEdge.second); |
| continue; |
| } |
| } |
| auto [startPoint, endPoint] = startAndEndPointsForCorner(fromEdge, toEdge, radius); |
| moveOrAddLineTo(startPoint); |
| |
| auto [cp1, cp2] = controlPointsForBezierCurve(corner, fromEdge, toEdge, radius); |
| path.addBezierCurveTo(cp1, cp2, endPoint); |
| } |
| path.closeSubpath(); |
| return path; |
| } |
| |
| |
| } |