• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5part of engine;
6
7/// Converts [path] to SVG path syntax to be used as "d" attribute in path
8/// element.
9void pathToSvg(ui.Path path, StringBuffer sb,
10    {double offsetX = 0, double offsetY = 0}) {
11  for (Subpath subPath in path.subpaths) {
12    for (PathCommand command in subPath.commands) {
13      switch (command.type) {
14        case PathCommandTypes.moveTo:
15          final MoveTo moveTo = command;
16          sb.write('M ${moveTo.x + offsetX} ${moveTo.y + offsetY}');
17          break;
18        case PathCommandTypes.lineTo:
19          final LineTo lineTo = command;
20          sb.write('L ${lineTo.x + offsetX} ${lineTo.y + offsetY}');
21          break;
22        case PathCommandTypes.bezierCurveTo:
23          final BezierCurveTo curve = command;
24          sb.write('C ${curve.x1 + offsetX} ${curve.y1 + offsetY} '
25              '${curve.x2 + offsetX} ${curve.y2 + offsetY} ${curve.x3 + offsetX} ${curve.y3 + offsetY}');
26          break;
27        case PathCommandTypes.quadraticCurveTo:
28          final QuadraticCurveTo quadraticCurveTo = command;
29          sb.write(
30              'Q ${quadraticCurveTo.x1 + offsetX} ${quadraticCurveTo.y1 + offsetY} '
31              '${quadraticCurveTo.x2 + offsetX} ${quadraticCurveTo.y2 + offsetY}');
32          break;
33        case PathCommandTypes.close:
34          sb.write('Z');
35          break;
36        case PathCommandTypes.ellipse:
37          final Ellipse ellipse = command;
38          // Handle edge case where start and end points are the same by drawing
39          // 2 half arcs.
40          if ((ellipse.endAngle - ellipse.startAngle) % (2 * math.pi) == 0.0) {
41            _writeEllipse(
42                sb,
43                ellipse.x + offsetX,
44                ellipse.y + offsetY,
45                ellipse.radiusX,
46                ellipse.radiusY,
47                ellipse.rotation,
48                -math.pi,
49                0,
50                ellipse.anticlockwise,
51                moveToStartPoint: true);
52            _writeEllipse(
53                sb,
54                ellipse.x + offsetX,
55                ellipse.y + offsetY,
56                ellipse.radiusX,
57                ellipse.radiusY,
58                ellipse.rotation,
59                0,
60                math.pi,
61                ellipse.anticlockwise);
62          } else {
63            _writeEllipse(
64                sb,
65                ellipse.x + offsetX,
66                ellipse.y + offsetY,
67                ellipse.radiusX,
68                ellipse.radiusY,
69                ellipse.rotation,
70                ellipse.startAngle,
71                ellipse.endAngle,
72                ellipse.anticlockwise);
73          }
74          break;
75        case PathCommandTypes.rRect:
76          final RRectCommand rrectCommand = command;
77          final ui.RRect rrect = rrectCommand.rrect;
78          double left = rrect.left + offsetX;
79          double right = rrect.right + offsetX;
80          double top = rrect.top + offsetY;
81          double bottom = rrect.bottom + offsetY;
82          if (left > right) {
83            left = right;
84            right = rrect.left + offsetX;
85          }
86          if (top > bottom) {
87            top = bottom;
88            bottom = rrect.top + offsetY;
89          }
90          final double trRadiusX = rrect.trRadiusX.abs();
91          final double tlRadiusX = rrect.tlRadiusX.abs();
92          final double trRadiusY = rrect.trRadiusY.abs();
93          final double tlRadiusY = rrect.tlRadiusY.abs();
94          final double blRadiusX = rrect.blRadiusX.abs();
95          final double brRadiusX = rrect.brRadiusX.abs();
96          final double blRadiusY = rrect.blRadiusY.abs();
97          final double brRadiusY = rrect.brRadiusY.abs();
98
99          sb.write('L ${left + trRadiusX} $top ');
100          // Top side and top-right corner
101          sb.write('M ${right - trRadiusX} $top ');
102          _writeEllipse(sb, right - trRadiusX, top + trRadiusY, trRadiusX,
103              trRadiusY, 0, 1.5 * math.pi, 2.0 * math.pi, false);
104          // Right side and bottom-right corner
105          sb.write('L $right ${bottom - brRadiusY} ');
106          _writeEllipse(sb, right - brRadiusX, bottom - brRadiusY, brRadiusX,
107              brRadiusY, 0, 0, 0.5 * math.pi, false);
108          // Bottom side and bottom-left corner
109          sb.write('L ${left + blRadiusX} $bottom ');
110          _writeEllipse(sb, left + blRadiusX, bottom - blRadiusY, blRadiusX,
111              blRadiusY, 0, 0.5 * math.pi, math.pi, false);
112          // Left side and top-left corner
113          sb.write('L $left ${top + tlRadiusY} ');
114          _writeEllipse(
115            sb,
116            left + tlRadiusX,
117            top + tlRadiusY,
118            tlRadiusX,
119            tlRadiusY,
120            0,
121            math.pi,
122            1.5 * math.pi,
123            false,
124          );
125          break;
126        case PathCommandTypes.rect:
127          final RectCommand rectCommand = command;
128          final bool horizontalSwap = rectCommand.width < 0;
129          final double left = offsetX +
130              (horizontalSwap
131                  ? rectCommand.x - rectCommand.width
132                  : rectCommand.x);
133          final double width =
134              horizontalSwap ? -rectCommand.width : rectCommand.width;
135          final bool verticalSwap = rectCommand.height < 0;
136          final double top = offsetY +
137              (verticalSwap
138                  ? rectCommand.y - rectCommand.height
139                  : rectCommand.y);
140          final double height =
141              verticalSwap ? -rectCommand.height : rectCommand.height;
142          sb.write('M $left $top ');
143          sb.write('L ${left + width} $top ');
144          sb.write('L ${left + width} ${top + height} ');
145          sb.write('L $left ${top + height} ');
146          sb.write('L $left $top ');
147          break;
148        default:
149          throw UnimplementedError('Unknown path command $command');
150      }
151    }
152  }
153}
154
155// See https://www.w3.org/TR/SVG/implnote.html B.2.3. Conversion from center to
156// endpoint parameterization.
157void _writeEllipse(
158    StringBuffer sb,
159    double cx,
160    double cy,
161    double radiusX,
162    double radiusY,
163    double rotation,
164    double startAngle,
165    double endAngle,
166    bool antiClockwise,
167    {bool moveToStartPoint = false}) {
168  final double cosRotation = math.cos(rotation);
169  final double sinRotation = math.sin(rotation);
170  final double x = math.cos(startAngle) * radiusX;
171  final double y = math.sin(startAngle) * radiusY;
172
173  final double startPx = cx + (cosRotation * x - sinRotation * y);
174  final double startPy = cy + (sinRotation * x + cosRotation * y);
175
176  final double xe = math.cos(endAngle) * radiusX;
177  final double ye = math.sin(endAngle) * radiusY;
178
179  final double endPx = cx + (cosRotation * xe - sinRotation * ye);
180  final double endPy = cy + (sinRotation * xe + cosRotation * ye);
181
182  final double delta = endAngle - startAngle;
183  final bool largeArc = delta.abs() > math.pi;
184
185  final double rotationDeg = rotation / math.pi * 180.0;
186  if (moveToStartPoint) {
187    sb.write('M $startPx $startPy ');
188  }
189  sb.write('A $radiusX $radiusY $rotationDeg '
190      '${largeArc ? 1 : 0} ${antiClockwise ? 0 : 1} $endPx $endPy');
191}
192