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