| 1 | /* |
| 2 | * Copyright (C) 2014-2015 Apple Inc. 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 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | |
| 27 | #include "config.h" |
| 28 | #include "PathUtilities.h" |
| 29 | |
| 30 | #include "AffineTransform.h" |
| 31 | #include "BorderData.h" |
| 32 | #include "FloatPoint.h" |
| 33 | #include "FloatRect.h" |
| 34 | #include "FloatRoundedRect.h" |
| 35 | #include "GeometryUtilities.h" |
| 36 | #include <math.h> |
| 37 | #include <wtf/MathExtras.h> |
| 38 | |
| 39 | namespace WebCore { |
| 40 | |
| 41 | class FloatPointGraph { |
| 42 | WTF_MAKE_NONCOPYABLE(FloatPointGraph); |
| 43 | public: |
| 44 | FloatPointGraph() { } |
| 45 | |
| 46 | class Node : public FloatPoint { |
| 47 | WTF_MAKE_NONCOPYABLE(Node); |
| 48 | public: |
| 49 | Node(FloatPoint point) |
| 50 | : FloatPoint(point) |
| 51 | { } |
| 52 | |
| 53 | const Vector<Node*>& nextPoints() const { return m_nextPoints; } |
| 54 | void addNextPoint(Node* node) |
| 55 | { |
| 56 | if (!m_nextPoints.contains(node)) |
| 57 | m_nextPoints.append(node); |
| 58 | } |
| 59 | |
| 60 | bool isVisited() const { return m_visited; } |
| 61 | void visit() { m_visited = true; } |
| 62 | |
| 63 | void reset() { m_visited = false; m_nextPoints.clear(); } |
| 64 | |
| 65 | private: |
| 66 | Vector<Node*> m_nextPoints; |
| 67 | bool m_visited { false }; |
| 68 | }; |
| 69 | |
| 70 | typedef std::pair<Node*, Node*> Edge; |
| 71 | typedef Vector<Edge> Polygon; |
| 72 | |
| 73 | Node* findOrCreateNode(FloatPoint); |
| 74 | |
| 75 | void reset() |
| 76 | { |
| 77 | for (auto& node : m_allNodes) |
| 78 | node->reset(); |
| 79 | } |
| 80 | |
| 81 | private: |
| 82 | Vector<std::unique_ptr<Node>> m_allNodes; |
| 83 | }; |
| 84 | |
| 85 | FloatPointGraph::Node* FloatPointGraph::findOrCreateNode(FloatPoint point) |
| 86 | { |
| 87 | for (auto& testNode : m_allNodes) { |
| 88 | if (areEssentiallyEqual(*testNode, point)) |
| 89 | return testNode.get(); |
| 90 | } |
| 91 | |
| 92 | m_allNodes.append(std::make_unique<FloatPointGraph::Node>(point)); |
| 93 | return m_allNodes.last().get(); |
| 94 | } |
| 95 | |
| 96 | static bool findLineSegmentIntersection(const FloatPointGraph::Edge& edgeA, const FloatPointGraph::Edge& edgeB, FloatPoint& intersectionPoint) |
| 97 | { |
| 98 | if (!findIntersection(*edgeA.first, *edgeA.second, *edgeB.first, *edgeB.second, intersectionPoint)) |
| 99 | return false; |
| 100 | |
| 101 | FloatPoint edgeAVec(*edgeA.second - *edgeA.first); |
| 102 | FloatPoint edgeBVec(*edgeB.second - *edgeB.first); |
| 103 | |
| 104 | float dotA = edgeAVec.dot(toFloatPoint(intersectionPoint - *edgeA.first)); |
| 105 | if (dotA < 0 || dotA > edgeAVec.lengthSquared()) |
| 106 | return false; |
| 107 | |
| 108 | float dotB = edgeBVec.dot(toFloatPoint(intersectionPoint - *edgeB.first)); |
| 109 | if (dotB < 0 || dotB > edgeBVec.lengthSquared()) |
| 110 | return false; |
| 111 | |
| 112 | return true; |
| 113 | } |
| 114 | |
| 115 | static bool addIntersectionPoints(Vector<FloatPointGraph::Polygon>& polys, FloatPointGraph& graph) |
| 116 | { |
| 117 | bool foundAnyIntersections = false; |
| 118 | |
| 119 | Vector<FloatPointGraph::Edge> allEdges; |
| 120 | for (auto& poly : polys) |
| 121 | allEdges.appendVector(poly); |
| 122 | |
| 123 | for (const FloatPointGraph::Edge& edgeA : allEdges) { |
| 124 | Vector<FloatPointGraph::Node*> intersectionPoints({edgeA.first, edgeA.second}); |
| 125 | |
| 126 | for (const FloatPointGraph::Edge& edgeB : allEdges) { |
| 127 | if (&edgeA == &edgeB) |
| 128 | continue; |
| 129 | |
| 130 | FloatPoint intersectionPoint; |
| 131 | if (!findLineSegmentIntersection(edgeA, edgeB, intersectionPoint)) |
| 132 | continue; |
| 133 | foundAnyIntersections = true; |
| 134 | intersectionPoints.append(graph.findOrCreateNode(intersectionPoint)); |
| 135 | } |
| 136 | |
| 137 | std::sort(intersectionPoints.begin(), intersectionPoints.end(), [edgeA](auto* a, auto* b) { |
| 138 | return FloatPoint(*edgeA.first - *b).lengthSquared() > FloatPoint(*edgeA.first - *a).lengthSquared(); |
| 139 | }); |
| 140 | |
| 141 | for (unsigned pointIndex = 1; pointIndex < intersectionPoints.size(); pointIndex++) |
| 142 | intersectionPoints[pointIndex - 1]->addNextPoint(intersectionPoints[pointIndex]); |
| 143 | } |
| 144 | |
| 145 | return foundAnyIntersections; |
| 146 | } |
| 147 | |
| 148 | static FloatPointGraph::Polygon walkGraphAndExtractPolygon(FloatPointGraph::Node* startNode) |
| 149 | { |
| 150 | FloatPointGraph::Polygon outPoly; |
| 151 | |
| 152 | FloatPointGraph::Node* currentNode = startNode; |
| 153 | FloatPointGraph::Node* previousNode = startNode; |
| 154 | |
| 155 | do { |
| 156 | currentNode->visit(); |
| 157 | |
| 158 | FloatPoint currentVec(*previousNode - *currentNode); |
| 159 | currentVec.normalize(); |
| 160 | |
| 161 | // Walk the graph, at each node choosing the next non-visited |
| 162 | // point with the greatest internal angle. |
| 163 | FloatPointGraph::Node* nextNode = nullptr; |
| 164 | float nextNodeAngle = 0; |
| 165 | for (auto* potentialNextNode : currentNode->nextPoints()) { |
| 166 | if (potentialNextNode == currentNode) |
| 167 | continue; |
| 168 | |
| 169 | // If we can get back to the start, we should, ignoring the fact that we already visited it. |
| 170 | // Otherwise we'll head inside the shape. |
| 171 | if (potentialNextNode == startNode) { |
| 172 | nextNode = startNode; |
| 173 | break; |
| 174 | } |
| 175 | |
| 176 | if (potentialNextNode->isVisited()) |
| 177 | continue; |
| 178 | |
| 179 | FloatPoint nextVec(*potentialNextNode - *currentNode); |
| 180 | nextVec.normalize(); |
| 181 | |
| 182 | float angle = acos(nextVec.dot(currentVec)); |
| 183 | float crossZ = nextVec.x() * currentVec.y() - nextVec.y() * currentVec.x(); |
| 184 | |
| 185 | if (crossZ < 0) |
| 186 | angle = (2 * piFloat) - angle; |
| 187 | |
| 188 | if (!nextNode || angle > nextNodeAngle) { |
| 189 | nextNode = potentialNextNode; |
| 190 | nextNodeAngle = angle; |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | // If we don't end up at a node adjacent to the starting node, |
| 195 | // something went wrong (there's probably a hole in the shape), |
| 196 | // so bail out. We'll use a bounding box instead. |
| 197 | if (!nextNode) |
| 198 | return FloatPointGraph::Polygon(); |
| 199 | |
| 200 | outPoly.append(std::make_pair(currentNode, nextNode)); |
| 201 | |
| 202 | previousNode = currentNode; |
| 203 | currentNode = nextNode; |
| 204 | } while (currentNode != startNode); |
| 205 | |
| 206 | return outPoly; |
| 207 | } |
| 208 | |
| 209 | static FloatPointGraph::Node* findUnvisitedPolygonStartPoint(Vector<FloatPointGraph::Polygon>& polys) |
| 210 | { |
| 211 | for (auto& poly : polys) { |
| 212 | for (auto& edge : poly) { |
| 213 | if (edge.first->isVisited() || edge.second->isVisited()) |
| 214 | goto nextPolygon; |
| 215 | } |
| 216 | |
| 217 | // FIXME: We should make sure we find an outside edge to start with. |
| 218 | return poly[0].first; |
| 219 | nextPolygon: |
| 220 | continue; |
| 221 | } |
| 222 | return nullptr; |
| 223 | } |
| 224 | |
| 225 | static Vector<FloatPointGraph::Polygon> unitePolygons(Vector<FloatPointGraph::Polygon>& polys, FloatPointGraph& graph) |
| 226 | { |
| 227 | graph.reset(); |
| 228 | |
| 229 | // There are no intersections, so the polygons are disjoint (we already removed wholly-contained rects in an earlier step). |
| 230 | if (!addIntersectionPoints(polys, graph)) |
| 231 | return polys; |
| 232 | |
| 233 | Vector<FloatPointGraph::Polygon> unitedPolygons; |
| 234 | |
| 235 | while (FloatPointGraph::Node* startNode = findUnvisitedPolygonStartPoint(polys)) { |
| 236 | FloatPointGraph::Polygon unitedPolygon = walkGraphAndExtractPolygon(startNode); |
| 237 | if (unitedPolygon.isEmpty()) |
| 238 | return Vector<FloatPointGraph::Polygon>(); |
| 239 | unitedPolygons.append(unitedPolygon); |
| 240 | } |
| 241 | |
| 242 | return unitedPolygons; |
| 243 | } |
| 244 | |
| 245 | static FloatPointGraph::Polygon edgesForRect(FloatRect rect, FloatPointGraph& graph) |
| 246 | { |
| 247 | auto minMin = graph.findOrCreateNode(rect.minXMinYCorner()); |
| 248 | auto minMax = graph.findOrCreateNode(rect.minXMaxYCorner()); |
| 249 | auto maxMax = graph.findOrCreateNode(rect.maxXMaxYCorner()); |
| 250 | auto maxMin = graph.findOrCreateNode(rect.maxXMinYCorner()); |
| 251 | |
| 252 | return FloatPointGraph::Polygon({ |
| 253 | std::make_pair(minMin, maxMin), |
| 254 | std::make_pair(maxMin, maxMax), |
| 255 | std::make_pair(maxMax, minMax), |
| 256 | std::make_pair(minMax, minMin) |
| 257 | }); |
| 258 | } |
| 259 | |
| 260 | static Vector<FloatPointGraph::Polygon> polygonsForRect(const Vector<FloatRect>& rects, FloatPointGraph& graph) |
| 261 | { |
| 262 | Vector<FloatRect> sortedRects = rects; |
| 263 | std::sort(sortedRects.begin(), sortedRects.end(), [](FloatRect a, FloatRect b) { return b.y() > a.y(); }); |
| 264 | |
| 265 | Vector<FloatPointGraph::Polygon> rectPolygons; |
| 266 | rectPolygons.reserveInitialCapacity(sortedRects.size()); |
| 267 | |
| 268 | for (auto& rect : sortedRects) { |
| 269 | bool isContained = false; |
| 270 | for (auto& otherRect : sortedRects) { |
| 271 | if (&rect == &otherRect) |
| 272 | continue; |
| 273 | if (otherRect.contains(rect)) { |
| 274 | isContained = true; |
| 275 | break; |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | if (!isContained) |
| 280 | rectPolygons.uncheckedAppend(edgesForRect(rect, graph)); |
| 281 | } |
| 282 | return unitePolygons(rectPolygons, graph); |
| 283 | } |
| 284 | |
| 285 | Vector<Path> PathUtilities::pathsWithShrinkWrappedRects(const Vector<FloatRect>& rects, float radius) |
| 286 | { |
| 287 | Vector<Path> paths; |
| 288 | |
| 289 | if (rects.isEmpty()) |
| 290 | return paths; |
| 291 | |
| 292 | if (rects.size() > 20) { |
| 293 | Path path; |
| 294 | for (const auto& rect : rects) |
| 295 | path.addRoundedRect(rect, FloatSize(radius, radius)); |
| 296 | paths.append(path); |
| 297 | return paths; |
| 298 | } |
| 299 | |
| 300 | FloatPointGraph graph; |
| 301 | Vector<FloatPointGraph::Polygon> polys = polygonsForRect(rects, graph); |
| 302 | if (polys.isEmpty()) { |
| 303 | Path path; |
| 304 | for (const auto& rect : rects) |
| 305 | path.addRoundedRect(rect, FloatSize(radius, radius)); |
| 306 | paths.append(path); |
| 307 | return paths; |
| 308 | } |
| 309 | |
| 310 | for (auto& poly : polys) { |
| 311 | Path path; |
| 312 | for (unsigned i = 0; i < poly.size(); ++i) { |
| 313 | FloatPointGraph::Edge& toEdge = poly[i]; |
| 314 | // Connect the first edge to the last. |
| 315 | FloatPointGraph::Edge& fromEdge = (i > 0) ? poly[i - 1] : poly[poly.size() - 1]; |
| 316 | |
| 317 | FloatPoint fromEdgeVec = toFloatPoint(*fromEdge.second - *fromEdge.first); |
| 318 | FloatPoint toEdgeVec = toFloatPoint(*toEdge.second - *toEdge.first); |
| 319 | |
| 320 | // Clamp the radius to no more than half the length of either adjacent edge, |
| 321 | // because we want a smooth curve and don't want unequal radii. |
| 322 | float clampedRadius = std::min(radius, fabsf(fromEdgeVec.x() ? fromEdgeVec.x() : fromEdgeVec.y()) / 2); |
| 323 | clampedRadius = std::min(clampedRadius, fabsf(toEdgeVec.x() ? toEdgeVec.x() : toEdgeVec.y()) / 2); |
| 324 | |
| 325 | FloatPoint fromEdgeNorm = fromEdgeVec; |
| 326 | fromEdgeNorm.normalize(); |
| 327 | FloatPoint toEdgeNorm = toEdgeVec; |
| 328 | toEdgeNorm.normalize(); |
| 329 | |
| 330 | // Project the radius along the incoming and outgoing edge. |
| 331 | FloatSize fromOffset = clampedRadius * toFloatSize(fromEdgeNorm); |
| 332 | FloatSize toOffset = clampedRadius * toFloatSize(toEdgeNorm); |
| 333 | |
| 334 | if (!i) |
| 335 | path.moveTo(*fromEdge.second - fromOffset); |
| 336 | else |
| 337 | path.addLineTo(*fromEdge.second - fromOffset); |
| 338 | path.addArcTo(*fromEdge.second, *toEdge.first + toOffset, clampedRadius); |
| 339 | } |
| 340 | path.closeSubpath(); |
| 341 | paths.append(path); |
| 342 | } |
| 343 | return paths; |
| 344 | } |
| 345 | |
| 346 | Path PathUtilities::pathWithShrinkWrappedRects(const Vector<FloatRect>& rects, float radius) |
| 347 | { |
| 348 | Vector<Path> paths = pathsWithShrinkWrappedRects(rects, radius); |
| 349 | |
| 350 | Path unionPath; |
| 351 | for (const auto& path : paths) |
| 352 | unionPath.addPath(path, AffineTransform()); |
| 353 | |
| 354 | return unionPath; |
| 355 | } |
| 356 | |
| 357 | static std::pair<FloatPoint, FloatPoint> startAndEndPointsForCorner(const FloatPointGraph::Edge& fromEdge, const FloatPointGraph::Edge& toEdge, const FloatSize& radius) |
| 358 | { |
| 359 | FloatPoint startPoint; |
| 360 | FloatPoint endPoint; |
| 361 | |
| 362 | FloatSize fromEdgeVector = *fromEdge.second - *fromEdge.first; |
| 363 | FloatSize toEdgeVector = *toEdge.second - *toEdge.first; |
| 364 | |
| 365 | FloatPoint fromEdgeNorm = toFloatPoint(fromEdgeVector); |
| 366 | fromEdgeNorm.normalize(); |
| 367 | FloatSize fromOffset = FloatSize(radius.width() * fromEdgeNorm.x(), radius.height() * fromEdgeNorm.y()); |
| 368 | startPoint = *fromEdge.second - fromOffset; |
| 369 | |
| 370 | FloatPoint toEdgeNorm = toFloatPoint(toEdgeVector); |
| 371 | toEdgeNorm.normalize(); |
| 372 | FloatSize toOffset = FloatSize(radius.width() * toEdgeNorm.x(), radius.height() * toEdgeNorm.y()); |
| 373 | endPoint = *toEdge.first + toOffset; |
| 374 | return std::make_pair(startPoint, endPoint); |
| 375 | } |
| 376 | |
| 377 | enum class CornerType { TopLeft, TopRight, BottomRight, BottomLeft, Other }; |
| 378 | static CornerType cornerType(const FloatPointGraph::Edge& fromEdge, const FloatPointGraph::Edge& toEdge) |
| 379 | { |
| 380 | auto fromEdgeVector = *fromEdge.second - *fromEdge.first; |
| 381 | auto toEdgeVector = *toEdge.second - *toEdge.first; |
| 382 | |
| 383 | if (fromEdgeVector.height() < 0 && toEdgeVector.width() > 0) |
| 384 | return CornerType::TopLeft; |
| 385 | if (fromEdgeVector.width() > 0 && toEdgeVector.height() > 0) |
| 386 | return CornerType::TopRight; |
| 387 | if (fromEdgeVector.height() > 0 && toEdgeVector.width() < 0) |
| 388 | return CornerType::BottomRight; |
| 389 | if (fromEdgeVector.width() < 0 && toEdgeVector.height() < 0) |
| 390 | return CornerType::BottomLeft; |
| 391 | return CornerType::Other; |
| 392 | } |
| 393 | |
| 394 | static CornerType cornerTypeForMultiline(const FloatPointGraph::Edge& fromEdge, const FloatPointGraph::Edge& toEdge, const Vector<FloatPoint>& corners) |
| 395 | { |
| 396 | auto corner = cornerType(fromEdge, toEdge); |
| 397 | if (corner == CornerType::TopLeft && corners.at(0) == *fromEdge.second) |
| 398 | return corner; |
| 399 | if (corner == CornerType::TopRight && corners.at(1) == *fromEdge.second) |
| 400 | return corner; |
| 401 | if (corner == CornerType::BottomRight && corners.at(2) == *fromEdge.second) |
| 402 | return corner; |
| 403 | if (corner == CornerType::BottomLeft && corners.at(3) == *fromEdge.second) |
| 404 | return corner; |
| 405 | return CornerType::Other; |
| 406 | } |
| 407 | |
| 408 | static std::pair<FloatPoint, FloatPoint> controlPointsForBezierCurve(CornerType cornerType, const FloatPointGraph::Edge& fromEdge, |
| 409 | const FloatPointGraph::Edge& toEdge, const FloatSize& radius) |
| 410 | { |
| 411 | FloatPoint cp1; |
| 412 | FloatPoint cp2; |
| 413 | switch (cornerType) { |
| 414 | case CornerType::TopLeft: { |
| 415 | cp1 = FloatPoint(fromEdge.second->x(), fromEdge.second->y() + radius.height() * Path::circleControlPoint()); |
| 416 | cp2 = FloatPoint(toEdge.first->x() + radius.width() * Path::circleControlPoint(), toEdge.first->y()); |
| 417 | break; |
| 418 | } |
| 419 | case CornerType::TopRight: { |
| 420 | cp1 = FloatPoint(fromEdge.second->x() - radius.width() * Path::circleControlPoint(), fromEdge.second->y()); |
| 421 | cp2 = FloatPoint(toEdge.first->x(), toEdge.first->y() + radius.height() * Path::circleControlPoint()); |
| 422 | break; |
| 423 | } |
| 424 | case CornerType::BottomRight: { |
| 425 | cp1 = FloatPoint(fromEdge.second->x(), fromEdge.second->y() - radius.height() * Path::circleControlPoint()); |
| 426 | cp2 = FloatPoint(toEdge.first->x() - radius.width() * Path::circleControlPoint(), toEdge.first->y()); |
| 427 | break; |
| 428 | } |
| 429 | case CornerType::BottomLeft: { |
| 430 | cp1 = FloatPoint(fromEdge.second->x() + radius.width() * Path::circleControlPoint(), fromEdge.second->y()); |
| 431 | cp2 = FloatPoint(toEdge.first->x(), toEdge.first->y() - radius.height() * Path::circleControlPoint()); |
| 432 | break; |
| 433 | } |
| 434 | case CornerType::Other: { |
| 435 | ASSERT_NOT_REACHED(); |
| 436 | break; |
| 437 | } |
| 438 | } |
| 439 | return std::make_pair(cp1, cp2); |
| 440 | } |
| 441 | |
| 442 | static FloatRoundedRect::Radii adjustedtRadiiForHuggingCurve(const FloatSize& topLeftRadius, const FloatSize& topRightRadius, |
| 443 | const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius, float outlineOffset) |
| 444 | { |
| 445 | FloatRoundedRect::Radii radii; |
| 446 | // This adjusts the radius so that it follows the border curve even when offset is present. |
| 447 | auto adjustedRadius = [outlineOffset](const FloatSize& radius) |
| 448 | { |
| 449 | FloatSize expandSize; |
| 450 | if (radius.width() > outlineOffset) |
| 451 | expandSize.setWidth(std::min(outlineOffset, radius.width() - outlineOffset)); |
| 452 | if (radius.height() > outlineOffset) |
| 453 | expandSize.setHeight(std::min(outlineOffset, radius.height() - outlineOffset)); |
| 454 | FloatSize adjustedRadius = radius; |
| 455 | adjustedRadius.expand(expandSize.width(), expandSize.height()); |
| 456 | // Do not go to negative radius. |
| 457 | return adjustedRadius.expandedTo(FloatSize(0, 0)); |
| 458 | }; |
| 459 | |
| 460 | radii.setTopLeft(adjustedRadius(topLeftRadius)); |
| 461 | radii.setTopRight(adjustedRadius(topRightRadius)); |
| 462 | radii.setBottomRight(adjustedRadius(bottomRightRadius)); |
| 463 | radii.setBottomLeft(adjustedRadius(bottomLeftRadius)); |
| 464 | return radii; |
| 465 | } |
| 466 | |
| 467 | static Optional<FloatRect> rectFromPolygon(const FloatPointGraph::Polygon& poly) |
| 468 | { |
| 469 | if (poly.size() != 4) |
| 470 | return Optional<FloatRect>(); |
| 471 | |
| 472 | Optional<FloatPoint> topLeft; |
| 473 | Optional<FloatPoint> bottomRight; |
| 474 | for (unsigned i = 0; i < poly.size(); ++i) { |
| 475 | const auto& toEdge = poly[i]; |
| 476 | const auto& fromEdge = (i > 0) ? poly[i - 1] : poly[poly.size() - 1]; |
| 477 | auto corner = cornerType(fromEdge, toEdge); |
| 478 | if (corner == CornerType::TopLeft) { |
| 479 | ASSERT(!topLeft); |
| 480 | topLeft = *fromEdge.second; |
| 481 | } else if (corner == CornerType::BottomRight) { |
| 482 | ASSERT(!bottomRight); |
| 483 | bottomRight = *fromEdge.second; |
| 484 | } |
| 485 | } |
| 486 | if (!topLeft || !bottomRight) |
| 487 | return Optional<FloatRect>(); |
| 488 | return FloatRect(topLeft.value(), bottomRight.value()); |
| 489 | } |
| 490 | |
| 491 | Path PathUtilities::pathWithShrinkWrappedRectsForOutline(const Vector<FloatRect>& rects, const BorderData& borderData, |
| 492 | float outlineOffset, TextDirection direction, WritingMode writingMode, float deviceScaleFactor) |
| 493 | { |
| 494 | ASSERT(borderData.hasBorderRadius()); |
| 495 | |
| 496 | FloatSize topLeftRadius { borderData.topLeft().width.value(), borderData.topLeft().height.value() }; |
| 497 | FloatSize topRightRadius { borderData.topRight().width.value(), borderData.topRight().height.value() }; |
| 498 | FloatSize bottomRightRadius { borderData.bottomRight().width.value(), borderData.bottomRight().height.value() }; |
| 499 | FloatSize bottomLeftRadius { borderData.bottomLeft().width.value(), borderData.bottomLeft().height.value() }; |
| 500 | |
| 501 | auto roundedRect = [topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius, outlineOffset, deviceScaleFactor] (const FloatRect& rect) |
| 502 | { |
| 503 | auto radii = adjustedtRadiiForHuggingCurve(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, outlineOffset); |
| 504 | radii.scale(calcBorderRadiiConstraintScaleFor(rect, radii)); |
| 505 | RoundedRect roundedRect(LayoutRect(rect), |
| 506 | RoundedRect::Radii(LayoutSize(radii.topLeft()), LayoutSize(radii.topRight()), LayoutSize(radii.bottomLeft()), LayoutSize(radii.bottomRight()))); |
| 507 | Path path; |
| 508 | path.addRoundedRect(roundedRect.pixelSnappedRoundedRectForPainting(deviceScaleFactor)); |
| 509 | return path; |
| 510 | }; |
| 511 | |
| 512 | if (rects.size() == 1) |
| 513 | return roundedRect(rects.at(0)); |
| 514 | |
| 515 | FloatPointGraph graph; |
| 516 | const auto polys = polygonsForRect(rects, graph); |
| 517 | // Fall back to corner painting with no radius for empty and disjoint rectangles. |
| 518 | if (!polys.size() || polys.size() > 1) |
| 519 | return Path(); |
| 520 | const auto& poly = polys.at(0); |
| 521 | // Fast path when poly has one rect only. |
| 522 | Optional<FloatRect> rect = rectFromPolygon(poly); |
| 523 | if (rect) |
| 524 | return roundedRect(rect.value()); |
| 525 | |
| 526 | Path path; |
| 527 | // Multiline outline needs to match multiline border painting. Only first and last lines are getting rounded borders. |
| 528 | auto isLeftToRight = isLeftToRightDirection(direction); |
| 529 | auto firstLineRect = isLeftToRight ? rects.at(0) : rects.at(rects.size() - 1); |
| 530 | auto lastLineRect = isLeftToRight ? rects.at(rects.size() - 1) : rects.at(0); |
| 531 | // Adjust radius so that it matches the box border. |
| 532 | auto firstLineRadii = FloatRoundedRect::Radii(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); |
| 533 | auto lastLineRadii = FloatRoundedRect::Radii(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); |
| 534 | firstLineRadii.scale(calcBorderRadiiConstraintScaleFor(firstLineRect, firstLineRadii)); |
| 535 | lastLineRadii.scale(calcBorderRadiiConstraintScaleFor(lastLineRect, lastLineRadii)); |
| 536 | topLeftRadius = firstLineRadii.topLeft(); |
| 537 | bottomLeftRadius = firstLineRadii.bottomLeft(); |
| 538 | topRightRadius = lastLineRadii.topRight(); |
| 539 | bottomRightRadius = lastLineRadii.bottomRight(); |
| 540 | Vector<FloatPoint> corners; |
| 541 | // physical topLeft/topRight/bottomRight/bottomLeft |
| 542 | auto isHorizontal = isHorizontalWritingMode(writingMode); |
| 543 | corners.append(firstLineRect.minXMinYCorner()); |
| 544 | corners.append(isHorizontal ? lastLineRect.maxXMinYCorner() : firstLineRect.maxXMinYCorner()); |
| 545 | corners.append(lastLineRect.maxXMaxYCorner()); |
| 546 | corners.append(isHorizontal ? firstLineRect.minXMaxYCorner() : lastLineRect.minXMaxYCorner()); |
| 547 | |
| 548 | for (unsigned i = 0; i < poly.size(); ++i) { |
| 549 | auto moveOrAddLineTo = [i, &path] (const FloatPoint& startPoint) |
| 550 | { |
| 551 | if (!i) |
| 552 | path.moveTo(startPoint); |
| 553 | else |
| 554 | path.addLineTo(startPoint); |
| 555 | }; |
| 556 | const auto& toEdge = poly[i]; |
| 557 | const auto& fromEdge = (i > 0) ? poly[i - 1] : poly[poly.size() - 1]; |
| 558 | FloatSize radius; |
| 559 | auto corner = cornerTypeForMultiline(fromEdge, toEdge, corners); |
| 560 | switch (corner) { |
| 561 | case CornerType::TopLeft: { |
| 562 | radius = topLeftRadius; |
| 563 | break; |
| 564 | } |
| 565 | case CornerType::TopRight: { |
| 566 | radius = topRightRadius; |
| 567 | break; |
| 568 | } |
| 569 | case CornerType::BottomRight: { |
| 570 | radius = bottomRightRadius; |
| 571 | break; |
| 572 | } |
| 573 | case CornerType::BottomLeft: { |
| 574 | radius = bottomLeftRadius; |
| 575 | break; |
| 576 | } |
| 577 | case CornerType::Other: { |
| 578 | // Do not apply border radius on corners that normal border painting skips. (multiline content) |
| 579 | moveOrAddLineTo(*fromEdge.second); |
| 580 | continue; |
| 581 | } |
| 582 | } |
| 583 | FloatPoint startPoint; |
| 584 | FloatPoint endPoint; |
| 585 | std::tie(startPoint, endPoint) = startAndEndPointsForCorner(fromEdge, toEdge, radius); |
| 586 | moveOrAddLineTo(startPoint); |
| 587 | |
| 588 | FloatPoint cp1; |
| 589 | FloatPoint cp2; |
| 590 | std::tie(cp1, cp2) = controlPointsForBezierCurve(corner, fromEdge, toEdge, radius); |
| 591 | path.addBezierCurveTo(cp1, cp2, endPoint); |
| 592 | } |
| 593 | path.closeSubpath(); |
| 594 | return path; |
| 595 | } |
| 596 | |
| 597 | |
| 598 | } |
| 599 | |