blob: be73c1e3911a0e49567899568adb1890883674b3 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.6
part of engine;
/// Converts [path] to SVG path syntax to be used as "d" attribute in path
/// element.
void pathToSvg(SurfacePath path, StringBuffer sb,
{double offsetX = 0, double offsetY = 0}) {
for (Subpath subPath in path.subpaths) {
for (PathCommand command in subPath.commands) {
switch (command.type) {
case PathCommandTypes.moveTo:
final MoveTo moveTo = command;
sb.write('M ${moveTo.x + offsetX} ${moveTo.y + offsetY}');
break;
case PathCommandTypes.lineTo:
final LineTo lineTo = command;
sb.write('L ${lineTo.x + offsetX} ${lineTo.y + offsetY}');
break;
case PathCommandTypes.bezierCurveTo:
final BezierCurveTo curve = command;
sb.write('C ${curve.x1 + offsetX} ${curve.y1 + offsetY} '
'${curve.x2 + offsetX} ${curve.y2 + offsetY} ${curve.x3 + offsetX} ${curve.y3 + offsetY}');
break;
case PathCommandTypes.quadraticCurveTo:
final QuadraticCurveTo quadraticCurveTo = command;
sb.write(
'Q ${quadraticCurveTo.x1 + offsetX} ${quadraticCurveTo.y1 + offsetY} '
'${quadraticCurveTo.x2 + offsetX} ${quadraticCurveTo.y2 + offsetY}');
break;
case PathCommandTypes.close:
sb.write('Z');
break;
case PathCommandTypes.ellipse:
final Ellipse ellipse = command;
// Handle edge case where start and end points are the same by drawing
// 2 half arcs.
if ((ellipse.endAngle - ellipse.startAngle) % (2 * math.pi) == 0.0) {
_writeEllipse(
sb,
ellipse.x + offsetX,
ellipse.y + offsetY,
ellipse.radiusX,
ellipse.radiusY,
ellipse.rotation,
-math.pi,
0,
ellipse.anticlockwise,
moveToStartPoint: true);
_writeEllipse(
sb,
ellipse.x + offsetX,
ellipse.y + offsetY,
ellipse.radiusX,
ellipse.radiusY,
ellipse.rotation,
0,
math.pi,
ellipse.anticlockwise);
} else {
_writeEllipse(
sb,
ellipse.x + offsetX,
ellipse.y + offsetY,
ellipse.radiusX,
ellipse.radiusY,
ellipse.rotation,
ellipse.startAngle,
ellipse.endAngle,
ellipse.anticlockwise);
}
break;
case PathCommandTypes.rRect:
final RRectCommand rrectCommand = command;
final ui.RRect rrect = rrectCommand.rrect;
double left = rrect.left + offsetX;
double right = rrect.right + offsetX;
double top = rrect.top + offsetY;
double bottom = rrect.bottom + offsetY;
if (left > right) {
left = right;
right = rrect.left + offsetX;
}
if (top > bottom) {
top = bottom;
bottom = rrect.top + offsetY;
}
final double trRadiusX = rrect.trRadiusX.abs();
final double tlRadiusX = rrect.tlRadiusX.abs();
final double trRadiusY = rrect.trRadiusY.abs();
final double tlRadiusY = rrect.tlRadiusY.abs();
final double blRadiusX = rrect.blRadiusX.abs();
final double brRadiusX = rrect.brRadiusX.abs();
final double blRadiusY = rrect.blRadiusY.abs();
final double brRadiusY = rrect.brRadiusY.abs();
sb.write('M ${left + trRadiusX} $top ');
// Top side and top-right corner
sb.write('L ${right - trRadiusX} $top ');
_writeEllipse(sb, right - trRadiusX, top + trRadiusY, trRadiusX,
trRadiusY, 0, 1.5 * math.pi, 2.0 * math.pi, false);
// Right side and bottom-right corner
sb.write('L $right ${bottom - brRadiusY} ');
_writeEllipse(sb, right - brRadiusX, bottom - brRadiusY, brRadiusX,
brRadiusY, 0, 0, 0.5 * math.pi, false);
// Bottom side and bottom-left corner
sb.write('L ${left + blRadiusX} $bottom ');
_writeEllipse(sb, left + blRadiusX, bottom - blRadiusY, blRadiusX,
blRadiusY, 0, 0.5 * math.pi, math.pi, false);
// Left side and top-left corner
sb.write('L $left ${top + tlRadiusY} ');
_writeEllipse(
sb,
left + tlRadiusX,
top + tlRadiusY,
tlRadiusX,
tlRadiusY,
0,
math.pi,
1.5 * math.pi,
false,
);
break;
case PathCommandTypes.rect:
final RectCommand rectCommand = command;
final bool horizontalSwap = rectCommand.width < 0;
final double left = offsetX +
(horizontalSwap
? rectCommand.x - rectCommand.width
: rectCommand.x);
final double width =
horizontalSwap ? -rectCommand.width : rectCommand.width;
final bool verticalSwap = rectCommand.height < 0;
final double top = offsetY +
(verticalSwap
? rectCommand.y - rectCommand.height
: rectCommand.y);
final double height =
verticalSwap ? -rectCommand.height : rectCommand.height;
sb.write('M $left $top ');
sb.write('L ${left + width} $top ');
sb.write('L ${left + width} ${top + height} ');
sb.write('L $left ${top + height} ');
sb.write('L $left $top ');
break;
default:
throw UnimplementedError('Unknown path command $command');
}
}
}
}
// See https://www.w3.org/TR/SVG/implnote.html B.2.3. Conversion from center to
// endpoint parameterization.
void _writeEllipse(
StringBuffer sb,
double cx,
double cy,
double radiusX,
double radiusY,
double rotation,
double startAngle,
double endAngle,
bool antiClockwise,
{bool moveToStartPoint = false}) {
final double cosRotation = math.cos(rotation);
final double sinRotation = math.sin(rotation);
final double x = math.cos(startAngle) * radiusX;
final double y = math.sin(startAngle) * radiusY;
final double startPx = cx + (cosRotation * x - sinRotation * y);
final double startPy = cy + (sinRotation * x + cosRotation * y);
final double xe = math.cos(endAngle) * radiusX;
final double ye = math.sin(endAngle) * radiusY;
final double endPx = cx + (cosRotation * xe - sinRotation * ye);
final double endPy = cy + (sinRotation * xe + cosRotation * ye);
final double delta = endAngle - startAngle;
final bool largeArc = delta.abs() > math.pi;
final double rotationDeg = rotation / math.pi * 180.0;
if (moveToStartPoint) {
sb.write('M $startPx $startPy ');
}
sb.write('A $radiusX $radiusY $rotationDeg '
'${largeArc ? 1 : 0} ${antiClockwise ? 0 : 1} $endPx $endPy');
}