1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SVGPathSegUtils.h"
9 #include "mozilla/ArrayUtils.h" // std::size
10 #include "mozilla/ServoStyleConsts.h" // StylePathCommand
11 #include "gfx2DGlue.h"
12 #include "SVGArcConverter.h"
13 #include "nsMathUtils.h"
14 #include "nsTextFormatter.h"
16 using namespace mozilla::gfx
;
20 static const float PATH_SEG_LENGTH_TOLERANCE
= 0.0000001f
;
21 static const uint32_t MAX_RECURSION
= 10;
23 static float CalcDistanceBetweenPoints(const Point
& aP1
, const Point
& aP2
) {
24 return NS_hypot(aP2
.x
- aP1
.x
, aP2
.y
- aP1
.y
);
27 static void SplitQuadraticBezier(const Point
* aCurve
, Point
* aLeft
,
29 aLeft
[0].x
= aCurve
[0].x
;
30 aLeft
[0].y
= aCurve
[0].y
;
31 aRight
[2].x
= aCurve
[2].x
;
32 aRight
[2].y
= aCurve
[2].y
;
33 aLeft
[1].x
= (aCurve
[0].x
+ aCurve
[1].x
) / 2;
34 aLeft
[1].y
= (aCurve
[0].y
+ aCurve
[1].y
) / 2;
35 aRight
[1].x
= (aCurve
[1].x
+ aCurve
[2].x
) / 2;
36 aRight
[1].y
= (aCurve
[1].y
+ aCurve
[2].y
) / 2;
37 aLeft
[2].x
= aRight
[0].x
= (aLeft
[1].x
+ aRight
[1].x
) / 2;
38 aLeft
[2].y
= aRight
[0].y
= (aLeft
[1].y
+ aRight
[1].y
) / 2;
41 static void SplitCubicBezier(const Point
* aCurve
, Point
* aLeft
, Point
* aRight
) {
43 tmp
.x
= (aCurve
[1].x
+ aCurve
[2].x
) / 4;
44 tmp
.y
= (aCurve
[1].y
+ aCurve
[2].y
) / 4;
45 aLeft
[0].x
= aCurve
[0].x
;
46 aLeft
[0].y
= aCurve
[0].y
;
47 aRight
[3].x
= aCurve
[3].x
;
48 aRight
[3].y
= aCurve
[3].y
;
49 aLeft
[1].x
= (aCurve
[0].x
+ aCurve
[1].x
) / 2;
50 aLeft
[1].y
= (aCurve
[0].y
+ aCurve
[1].y
) / 2;
51 aRight
[2].x
= (aCurve
[2].x
+ aCurve
[3].x
) / 2;
52 aRight
[2].y
= (aCurve
[2].y
+ aCurve
[3].y
) / 2;
53 aLeft
[2].x
= aLeft
[1].x
/ 2 + tmp
.x
;
54 aLeft
[2].y
= aLeft
[1].y
/ 2 + tmp
.y
;
55 aRight
[1].x
= aRight
[2].x
/ 2 + tmp
.x
;
56 aRight
[1].y
= aRight
[2].y
/ 2 + tmp
.y
;
57 aLeft
[3].x
= aRight
[0].x
= (aLeft
[2].x
+ aRight
[1].x
) / 2;
58 aLeft
[3].y
= aRight
[0].y
= (aLeft
[2].y
+ aRight
[1].y
) / 2;
61 static float CalcBezLengthHelper(const Point
* aCurve
, uint32_t aNumPts
,
62 uint32_t aRecursionCount
,
63 void (*aSplit
)(const Point
*, Point
*, Point
*)) {
66 float length
= 0, dist
;
67 for (uint32_t i
= 0; i
< aNumPts
- 1; i
++) {
68 length
+= CalcDistanceBetweenPoints(aCurve
[i
], aCurve
[i
+ 1]);
70 dist
= CalcDistanceBetweenPoints(aCurve
[0], aCurve
[aNumPts
- 1]);
71 if (length
- dist
> PATH_SEG_LENGTH_TOLERANCE
&&
72 aRecursionCount
< MAX_RECURSION
) {
73 aSplit(aCurve
, left
, right
);
75 return CalcBezLengthHelper(left
, aNumPts
, aRecursionCount
, aSplit
) +
76 CalcBezLengthHelper(right
, aNumPts
, aRecursionCount
, aSplit
);
81 static inline float CalcLengthOfCubicBezier(const Point
& aPos
,
85 Point curve
[4] = {aPos
, aCP1
, aCP2
, aTo
};
86 return CalcBezLengthHelper(curve
, 4, 0, SplitCubicBezier
);
89 static inline float CalcLengthOfQuadraticBezier(const Point
& aPos
,
92 Point curve
[3] = {aPos
, aCP
, aTo
};
93 return CalcBezLengthHelper(curve
, 3, 0, SplitQuadraticBezier
);
96 // Basically, this is just a variant version of the above TraverseXXX functions.
97 // We just put those function inside this and use StylePathCommand instead.
98 // This function and the above ones should be dropped by Bug 1388931.
100 void SVGPathSegUtils::TraversePathSegment(const StylePathCommand
& aCommand
,
101 SVGPathTraversalState
& aState
) {
102 switch (aCommand
.tag
) {
103 case StylePathCommand::Tag::Close
:
104 if (aState
.ShouldUpdateLengthAndControlPoints()) {
105 aState
.length
+= CalcDistanceBetweenPoints(aState
.pos
, aState
.start
);
106 aState
.cp1
= aState
.cp2
= aState
.start
;
108 aState
.pos
= aState
.start
;
110 case StylePathCommand::Tag::Move
: {
111 const Point
& p
= aCommand
.move
.point
.ToGfxPoint();
112 aState
.start
= aState
.pos
=
113 aCommand
.move
.by_to
== StyleByTo::To
? p
: aState
.pos
+ p
;
114 if (aState
.ShouldUpdateLengthAndControlPoints()) {
115 // aState.length is unchanged, since move commands don't affect path=
117 aState
.cp1
= aState
.cp2
= aState
.start
;
121 case StylePathCommand::Tag::Line
: {
122 Point to
= aCommand
.line
.by_to
== StyleByTo::To
123 ? aCommand
.line
.point
.ToGfxPoint()
124 : aState
.pos
+ aCommand
.line
.point
.ToGfxPoint();
125 if (aState
.ShouldUpdateLengthAndControlPoints()) {
126 aState
.length
+= CalcDistanceBetweenPoints(aState
.pos
, to
);
127 aState
.cp1
= aState
.cp2
= to
;
132 case StylePathCommand::Tag::CubicCurve
: {
133 const bool isRelative
= aCommand
.cubic_curve
.by_to
== StyleByTo::By
;
134 Point to
= isRelative
135 ? aState
.pos
+ aCommand
.cubic_curve
.point
.ToGfxPoint()
136 : aCommand
.cubic_curve
.point
.ToGfxPoint();
137 if (aState
.ShouldUpdateLengthAndControlPoints()) {
138 Point cp1
= aCommand
.cubic_curve
.control1
.ToGfxPoint();
139 Point cp2
= aCommand
.cubic_curve
.control2
.ToGfxPoint();
145 (float)CalcLengthOfCubicBezier(aState
.pos
, cp1
, cp2
, to
);
152 case StylePathCommand::Tag::QuadCurve
: {
153 const bool isRelative
= aCommand
.quad_curve
.by_to
== StyleByTo::By
;
154 Point to
= isRelative
155 ? aState
.pos
+ aCommand
.quad_curve
.point
.ToGfxPoint()
156 : aCommand
.quad_curve
.point
.ToGfxPoint();
157 if (aState
.ShouldUpdateLengthAndControlPoints()) {
158 Point cp
= isRelative
159 ? aState
.pos
+ aCommand
.quad_curve
.control1
.ToGfxPoint()
160 : aCommand
.quad_curve
.control1
.ToGfxPoint();
161 aState
.length
+= (float)CalcLengthOfQuadraticBezier(aState
.pos
, cp
, to
);
168 case StylePathCommand::Tag::Arc
: {
169 const auto& arc
= aCommand
.arc
;
170 Point to
= arc
.by_to
== StyleByTo::To
171 ? arc
.point
.ToGfxPoint()
172 : aState
.pos
+ arc
.point
.ToGfxPoint();
173 if (aState
.ShouldUpdateLengthAndControlPoints()) {
175 Point radii
= arc
.radii
.ToGfxPoint();
176 if (radii
.x
== 0.0f
|| radii
.y
== 0.0f
) {
177 dist
= CalcDistanceBetweenPoints(aState
.pos
, to
);
179 Point bez
[4] = {aState
.pos
, Point(0, 0), Point(0, 0), Point(0, 0)};
180 const bool largeArcFlag
= arc
.arc_size
== StyleArcSize::Large
;
181 const bool sweepFlag
= arc
.arc_sweep
== StyleArcSweep::Cw
;
182 SVGArcConverter
converter(aState
.pos
, to
, radii
, arc
.rotate
,
183 largeArcFlag
, sweepFlag
);
184 while (converter
.GetNextSegment(&bez
[1], &bez
[2], &bez
[3])) {
185 dist
+= CalcBezLengthHelper(bez
, 4, 0, SplitCubicBezier
);
189 aState
.length
+= dist
;
190 aState
.cp1
= aState
.cp2
= to
;
195 case StylePathCommand::Tag::HLine
: {
196 Point
to(aCommand
.h_line
.by_to
== StyleByTo::To
198 : aState
.pos
.x
+ aCommand
.h_line
.x
,
200 if (aState
.ShouldUpdateLengthAndControlPoints()) {
201 aState
.length
+= std::fabs(to
.x
- aState
.pos
.x
);
202 aState
.cp1
= aState
.cp2
= to
;
207 case StylePathCommand::Tag::VLine
: {
208 Point
to(aState
.pos
.x
, aCommand
.v_line
.by_to
== StyleByTo::To
210 : aState
.pos
.y
+ aCommand
.v_line
.y
);
211 if (aState
.ShouldUpdateLengthAndControlPoints()) {
212 aState
.length
+= std::fabs(to
.y
- aState
.pos
.y
);
213 aState
.cp1
= aState
.cp2
= to
;
218 case StylePathCommand::Tag::SmoothCubic
: {
219 const bool isRelative
= aCommand
.smooth_cubic
.by_to
== StyleByTo::By
;
220 Point to
= isRelative
221 ? aState
.pos
+ aCommand
.smooth_cubic
.point
.ToGfxPoint()
222 : aCommand
.smooth_cubic
.point
.ToGfxPoint();
223 if (aState
.ShouldUpdateLengthAndControlPoints()) {
224 Point cp1
= aState
.pos
- (aState
.cp2
- aState
.pos
);
225 Point cp2
= isRelative
? aState
.pos
+
226 aCommand
.smooth_cubic
.control2
.ToGfxPoint()
227 : aCommand
.smooth_cubic
.control2
.ToGfxPoint();
229 (float)CalcLengthOfCubicBezier(aState
.pos
, cp1
, cp2
, to
);
236 case StylePathCommand::Tag::SmoothQuad
: {
237 Point to
= aCommand
.smooth_quad
.by_to
== StyleByTo::To
238 ? aCommand
.smooth_quad
.point
.ToGfxPoint()
239 : aState
.pos
+ aCommand
.smooth_quad
.point
.ToGfxPoint();
240 if (aState
.ShouldUpdateLengthAndControlPoints()) {
241 Point cp
= aState
.pos
- (aState
.cp1
- aState
.pos
);
242 aState
.length
+= (float)CalcLengthOfQuadraticBezier(aState
.pos
, cp
, to
);
252 // Possible directions of an edge that doesn't immediately disqualify the path
259 // NONE represents (almost) zero-length edges, they should be ignored.
263 Maybe
<EdgeDir
> GetDirection(Point v
) {
264 if (!std::isfinite(v
.x
.value
) || !std::isfinite(v
.y
.value
)) {
268 bool x
= fabs(v
.x
) > 0.001;
269 bool y
= fabs(v
.y
) > 0.001;
275 return Some(EdgeDir::NONE
);
279 return Some(v
.x
> 0.0 ? EdgeDir::RIGHT
: EdgeDir::LEFT
);
282 return Some(v
.y
> 0.0 ? EdgeDir::DOWN
: EdgeDir::UP
);
285 EdgeDir
OppositeDirection(EdgeDir dir
) {
288 return EdgeDir::RIGHT
;
290 return EdgeDir::LEFT
;
292 return EdgeDir::DOWN
;
296 return EdgeDir::NONE
;
300 struct IsRectHelper
{
304 // Index of the next corner.
308 bool Edge(Point from
, Point to
) {
309 auto edge
= to
- from
;
311 auto maybeDir
= GetDirection(edge
);
312 if (maybeDir
.isNothing()) {
316 EdgeDir dir
= maybeDir
.value();
318 if (dir
== EdgeDir::NONE
) {
319 // zero-length edges aren't an issue.
323 if (dir
!= currentDir
) {
324 // The edge forms a corner with the previous edge.
326 // We are at the 5th corner, can't be a rectangle.
330 if (dir
== OppositeDirection(currentDir
)) {
331 // Can turn left or right but not a full 180 degrees.
340 min
.x
= fmin(min
.x
, to
.x
);
341 min
.y
= fmin(min
.y
, to
.y
);
342 max
.x
= fmax(max
.x
, to
.x
);
343 max
.y
= fmax(max
.y
, to
.y
);
353 if (dirs
[0] != OppositeDirection(dirs
[2]) ||
354 dirs
[1] != OppositeDirection(dirs
[3])) {
362 bool ApproxEqual(gfx::Point a
, gfx::Point b
) {
364 return fabs(v
.x
) < 0.001 && fabs(v
.y
) < 0.001;
367 Maybe
<gfx::Rect
> SVGPathToAxisAlignedRect(Span
<const StylePathCommand
> aPath
) {
368 Point
pathStart(0.0, 0.0);
369 Point
segStart(0.0, 0.0);
370 IsRectHelper helper
= {
375 {EdgeDir::NONE
, EdgeDir::NONE
, EdgeDir::NONE
, EdgeDir::NONE
},
378 for (const StylePathCommand
& cmd
: aPath
) {
380 case StylePathCommand::Tag::Move
: {
381 Point to
= cmd
.move
.point
.ToGfxPoint();
382 if (helper
.idx
!= 0) {
383 // This is overly strict since empty moveto sequences such as "M 10 12
384 // M 3 2 M 0 0" render nothing, but I expect it won't make us miss a
385 // lot of rect-shaped paths in practice and lets us avoidhandling
386 // special caps for empty sub-paths like "M 0 0 L 0 0" and "M 1 2 Z".
390 if (!ApproxEqual(pathStart
, segStart
)) {
391 // If we were only interested in filling we could auto-close here
392 // by calling helper.Edge like in the ClosePath case and detect some
393 // unclosed paths as rectangles.
396 // - "M 1 0 L 0 0 L 0 1 L 1 1 L 1 0" are both rects for filling and
398 // - "M 1 0 L 0 0 L 0 1 L 1 1" fills a rect but the stroke is shaped
403 if (helper
.idx
!= 0 && !helper
.EndSubpath()) {
407 if (cmd
.move
.by_to
== StyleByTo::By
) {
413 if (helper
.idx
== 0) {
420 case StylePathCommand::Tag::Close
: {
421 if (!helper
.Edge(segStart
, pathStart
)) {
424 if (!helper
.EndSubpath()) {
427 pathStart
= segStart
;
430 case StylePathCommand::Tag::Line
: {
431 Point to
= cmd
.line
.point
.ToGfxPoint();
432 if (cmd
.line
.by_to
== StyleByTo::By
) {
436 if (!helper
.Edge(segStart
, to
)) {
442 case StylePathCommand::Tag::HLine
: {
443 Point to
= gfx::Point(cmd
.h_line
.x
, segStart
.y
);
444 if (cmd
.h_line
.by_to
== StyleByTo::By
) {
448 if (!helper
.Edge(segStart
, to
)) {
454 case StylePathCommand::Tag::VLine
: {
455 Point to
= gfx::Point(segStart
.x
, cmd
.v_line
.y
);
456 if (cmd
.h_line
.by_to
== StyleByTo::By
) {
460 if (!helper
.Edge(segStart
, to
)) {
471 if (!ApproxEqual(pathStart
, segStart
)) {
472 // Same situation as with moveto regarding stroking not fullly closed path
473 // even though the fill is a rectangle.
477 if (!helper
.EndSubpath()) {
481 auto size
= (helper
.max
- helper
.min
);
482 return Some(Rect(helper
.min
, Size(size
.x
, size
.y
)));
485 } // namespace mozilla