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 | |