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
39namespace WebCore {
40
41class FloatPointGraph {
42 WTF_MAKE_NONCOPYABLE(FloatPointGraph);
43public:
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
81private:
82 Vector<std::unique_ptr<Node>> m_allNodes;
83};
84
85FloatPointGraph::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
96static 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
115static 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
148static 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
209static 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
225static 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
245static 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
260static 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
285Vector<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
346Path 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
357static 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
377enum class CornerType { TopLeft, TopRight, BottomRight, BottomLeft, Other };
378static 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
394static 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
408static 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
442static 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
467static 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
491Path 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