blob: f74a112ee9c73823ebb7f71d63ebc69bcce11f75 [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.12
import 'dart:math' as math;
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/ui.dart' hide window;
import 'package:ui/src/engine.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
/// Test winding and convexity of a path.
void testMain() {
group('Convexity', () {
test('Empty path should be convex', () {
final SurfacePath path = SurfacePath();
expect(path.isConvex, true);
});
test('Circle should be convex', () {
final SurfacePath path = SurfacePath();
path.addOval(Rect.fromLTRB(0, 0, 20, 20));
expect(path.isConvex, true);
// 2nd circle.
path.addOval(Rect.fromLTRB(0, 0, 20, 20));
expect(path.isConvex, false);
});
test('addRect should be convex', () {
SurfacePath path = SurfacePath();
path.addRect(Rect.fromLTRB(0, 0, 20, 20));
assert(path.isConvex, true);
path = SurfacePath();
path.addRectWithDirection(
Rect.fromLTRB(0, 0, 20, 20), SPathDirection.kCW, 0);
assert(path.isConvex, true);
path = SurfacePath();
path.addRectWithDirection(
Rect.fromLTRB(0, 0, 20, 20), SPathDirection.kCCW, 0);
assert(path.isConvex, true);
});
test('Quad should be convex', () {
final SurfacePath path = SurfacePath();
path.quadraticBezierTo(100, 100, 50, 50);
expect(path.isConvex, true);
});
test('moveto/lineto convexity', () {
final List<LineTestCase> testCases = <LineTestCase>[
LineTestCase('', SPathConvexityType.kConvex, SPathDirection.kUnknown),
LineTestCase(
'0 0', SPathConvexityType.kConvex, SPathDirection.kUnknown),
LineTestCase(
'0 0 10 10', SPathConvexityType.kConvex, SPathDirection.kUnknown),
LineTestCase('0 0 10 10 20 20 0 0 10 10', SPathConvexityType.kConcave,
SPathDirection.kUnknown),
LineTestCase(
'0 0 10 10 10 20', SPathConvexityType.kConvex, SPathDirection.kCW),
LineTestCase(
'0 0 10 10 10 0', SPathConvexityType.kConvex, SPathDirection.kCCW),
LineTestCase('0 0 10 10 10 0 0 10', SPathConvexityType.kConcave, null),
LineTestCase('0 0 10 0 0 10 -10 -10', SPathConvexityType.kConcave,
SPathDirection.kCW),
];
for (LineTestCase testCase in testCases) {
final SurfacePath path = SurfacePath();
setFromString(path, testCase.pathContent);
expect(path.convexityType, testCase.convexity);
}
});
test('Convexity of path with infinite points should return unknown', () {
final List<Offset> nonFinitePts = <Offset>[
Offset(double.infinity, 0),
Offset(0, double.infinity),
Offset(double.infinity, double.infinity),
Offset(double.negativeInfinity, 0),
Offset(0, double.negativeInfinity),
Offset(double.negativeInfinity, double.negativeInfinity),
Offset(double.negativeInfinity, double.infinity),
Offset(double.infinity, double.negativeInfinity),
Offset(double.nan, 0),
Offset(0, double.nan),
Offset(double.nan, double.nan)
];
final int nonFinitePointsCount = nonFinitePts.length;
final List<Offset> axisAlignedPts = <Offset>[
Offset(kScalarMax, 0),
Offset(0, kScalarMax),
Offset(kScalarMin, 0),
Offset(0, kScalarMin)
];
final int axisAlignedPointsCount = axisAlignedPts.length;
final SurfacePath path = SurfacePath();
for (int index = 0;
index < (13 * nonFinitePointsCount * axisAlignedPointsCount);
index++) {
final int i = index % nonFinitePointsCount;
final int f = index % axisAlignedPointsCount;
final int g = (f + 1) % axisAlignedPointsCount;
path.reset();
switch (index % 13) {
case 0:
path.lineTo(nonFinitePts[i].dx, nonFinitePts[i].dy);
break;
case 1:
path.quadraticBezierTo(nonFinitePts[i].dx, nonFinitePts[i].dy,
nonFinitePts[i].dx, nonFinitePts[i].dy);
break;
case 2:
path.quadraticBezierTo(nonFinitePts[i].dx, nonFinitePts[i].dy,
axisAlignedPts[f].dx, axisAlignedPts[f].dy);
break;
case 3:
path.quadraticBezierTo(axisAlignedPts[f].dx, axisAlignedPts[f].dy,
nonFinitePts[i].dx, nonFinitePts[i].dy);
break;
case 4:
path.cubicTo(
nonFinitePts[i].dx,
nonFinitePts[i].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy);
break;
case 5:
path.cubicTo(
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
nonFinitePts[i].dx,
nonFinitePts[i].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy);
break;
case 6:
path.cubicTo(
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
nonFinitePts[i].dx,
nonFinitePts[i].dy);
break;
case 7:
path.cubicTo(
nonFinitePts[i].dx,
nonFinitePts[i].dy,
nonFinitePts[i].dx,
nonFinitePts[i].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy);
break;
case 8:
path.cubicTo(
nonFinitePts[i].dx,
nonFinitePts[i].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
nonFinitePts[i].dx,
nonFinitePts[i].dy);
break;
case 9:
path.cubicTo(
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
nonFinitePts[i].dx,
nonFinitePts[i].dy,
nonFinitePts[i].dx,
nonFinitePts[i].dy);
break;
case 10:
path.cubicTo(
nonFinitePts[i].dx,
nonFinitePts[i].dy,
nonFinitePts[i].dx,
nonFinitePts[i].dy,
nonFinitePts[i].dx,
nonFinitePts[i].dy);
break;
case 11:
path.cubicTo(
nonFinitePts[i].dx,
nonFinitePts[i].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[g].dx,
axisAlignedPts[g].dy);
break;
case 12:
path.moveTo(nonFinitePts[i].dx, nonFinitePts[i].dy);
break;
}
expect(path.convexityType, SPathConvexityType.kUnknown);
}
for (int index = 0; index < (11 * axisAlignedPointsCount); ++index) {
int f = index % axisAlignedPointsCount;
int g = (f + 1) % axisAlignedPointsCount;
path.reset();
int curveSelect = index % 11;
switch (curveSelect) {
case 0:
path.moveTo(axisAlignedPts[f].dx, axisAlignedPts[f].dy);
break;
case 1:
path.lineTo(axisAlignedPts[f].dx, axisAlignedPts[f].dy);
break;
case 2:
path.quadraticBezierTo(axisAlignedPts[f].dx, axisAlignedPts[f].dy,
axisAlignedPts[f].dx, axisAlignedPts[f].dy);
break;
case 3:
path.quadraticBezierTo(axisAlignedPts[f].dx, axisAlignedPts[f].dy,
axisAlignedPts[g].dx, axisAlignedPts[g].dy);
break;
case 4:
path.quadraticBezierTo(axisAlignedPts[g].dx, axisAlignedPts[g].dy,
axisAlignedPts[f].dx, axisAlignedPts[f].dy);
break;
case 5:
path.cubicTo(
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy);
break;
case 6:
path.cubicTo(
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[g].dx,
axisAlignedPts[g].dy);
break;
case 7:
path.cubicTo(
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[g].dx,
axisAlignedPts[g].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy);
break;
case 8:
path.cubicTo(
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[g].dx,
axisAlignedPts[g].dy,
axisAlignedPts[g].dx,
axisAlignedPts[g].dy);
break;
case 9:
path.cubicTo(
axisAlignedPts[g].dx,
axisAlignedPts[g].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy);
break;
case 10:
path.cubicTo(
axisAlignedPts[g].dx,
axisAlignedPts[g].dy,
axisAlignedPts[f].dx,
axisAlignedPts[f].dy,
axisAlignedPts[g].dx,
axisAlignedPts[g].dy);
break;
}
if (curveSelect != 7 && curveSelect != 10) {
int result = path.convexityType;
expect(result, SPathConvexityType.kConvex);
} else {
// we make a copy so that we don't cache the result on the passed
// in path.
SurfacePath path2 = SurfacePath.from(path);
int c = path2.convexityType;
assert(SPathConvexityType.kUnknown == c ||
SPathConvexityType.kConcave == c);
}
}
});
test('Concave lines path', () {
final SurfacePath path = SurfacePath();
path.moveTo(-0.284071773, -0.0622361786);
path.lineTo(-0.284072, -0.0622351);
path.lineTo(-0.28407, -0.0622307);
path.lineTo(-0.284067, -0.0622182);
path.lineTo(-0.284084, -0.0622269);
path.lineTo(-0.284072, -0.0622362);
path.close();
expect(path.convexityType, SPathConvexityType.kConcave);
});
test('Single moveTo origin', () {
final SurfacePath path = SurfacePath();
path.moveTo(0, 0);
expect(path.convexityType, SPathConvexityType.kConvex);
});
test('Single diagonal line', () {
final SurfacePath path = SurfacePath();
path.moveTo(12, 20);
path.lineTo(-12, -20);
expect(path.convexityType, SPathConvexityType.kConvex);
});
test('TriLeft', () {
final SurfacePath path = SurfacePath();
path.moveTo(0, 0);
path.lineTo(1, 0);
path.lineTo(1, 1);
path.close();
expect(path.convexityType, SPathConvexityType.kConvex);
});
test('TriRight', () {
final SurfacePath path = SurfacePath();
path.moveTo(0, 0);
path.lineTo(-1, 0);
path.lineTo(1, 1);
path.close();
expect(path.convexityType, SPathConvexityType.kConvex);
});
test('square', () {
final SurfacePath path = SurfacePath();
path.moveTo(0, 0);
path.lineTo(1, 0);
path.lineTo(1, 1);
path.lineTo(0, 1);
path.close();
expect(path.convexityType, SPathConvexityType.kConvex);
});
test('redundant square', () {
final SurfacePath redundantSquare = SurfacePath();
redundantSquare.moveTo(0, 0);
redundantSquare.lineTo(0, 0);
redundantSquare.lineTo(0, 0);
redundantSquare.lineTo(1, 0);
redundantSquare.lineTo(1, 0);
redundantSquare.lineTo(1, 0);
redundantSquare.lineTo(1, 1);
redundantSquare.lineTo(1, 1);
redundantSquare.lineTo(1, 1);
redundantSquare.lineTo(0, 1);
redundantSquare.lineTo(0, 1);
redundantSquare.lineTo(0, 1);
redundantSquare.close();
expect(redundantSquare.convexityType, SPathConvexityType.kConvex);
});
test('bowtie', () {
final SurfacePath bowTie = SurfacePath();
bowTie.moveTo(0, 0);
bowTie.lineTo(0, 0);
bowTie.lineTo(0, 0);
bowTie.lineTo(1, 1);
bowTie.lineTo(1, 1);
bowTie.lineTo(1, 1);
bowTie.lineTo(1, 0);
bowTie.lineTo(1, 0);
bowTie.lineTo(1, 0);
bowTie.lineTo(0, 1);
bowTie.lineTo(0, 1);
bowTie.lineTo(0, 1);
bowTie.close();
expect(bowTie.convexityType, SPathConvexityType.kConcave);
});
test('sprial', () {
final SurfacePath spiral = SurfacePath();
spiral.moveTo(0, 0);
spiral.lineTo(100, 0);
spiral.lineTo(100, 100);
spiral.lineTo(0, 100);
spiral.lineTo(0, 50);
spiral.lineTo(50, 50);
spiral.lineTo(50, 75);
spiral.close();
expect(spiral.convexityType, SPathConvexityType.kConcave);
});
test('dent', () {
final SurfacePath dent = SurfacePath();
dent.moveTo(0, 0);
dent.lineTo(100, 100);
dent.lineTo(0, 100);
dent.lineTo(-50, 200);
dent.lineTo(-200, 100);
dent.close();
expect(dent.convexityType, SPathConvexityType.kConcave);
});
test('degenerate segments1', () {
final SurfacePath strokedSin = SurfacePath();
for (int i = 0; i < 2000; i++) {
double x = i.toDouble() / 2.0;
double y = 500 - (x + math.sin(x / 100) * 40) / 3;
if (0 == i) {
strokedSin.moveTo(x, y);
} else {
strokedSin.lineTo(x, y);
}
}
expect(strokedSin.convexityType, SPathConvexityType.kConcave);
});
/// Regression test for https://github.com/flutter/flutter/issues/66560.
test('Quadratic', () {
final SurfacePath path = SurfacePath();
path.moveTo(100.0, 0.0);
path.quadraticBezierTo(200.0, 0.0, 200.0, 100.0);
path.quadraticBezierTo(200.0, 200.0, 100.0, 200.0);
path.quadraticBezierTo(0.0, 200.0, 0.0, 100.0);
path.quadraticBezierTo(0.0, 0.0, 100.0, 0.0);
path.close();
expect(path.contains(Offset(100, 20)), true);
expect(path.contains(Offset(100, 120)), true);
expect(path.contains(Offset(100, -10)), false);
});
});
}
class LineTestCase {
final String pathContent;
final int convexity;
final int? direction;
LineTestCase(this.pathContent, this.convexity, this.direction);
}
/// Parses a string of the format "mx my lx1 ly1 lx2 ly2..." into a path
/// with moveTo/lineTo instructions for points.
void setFromString(SurfacePath path, String value) {
bool first = true;
List<String> points = value.split(' ');
if (points.length < 2) {
return;
}
for (int i = 0; i < points.length; i += 2) {
if (first) {
path.moveTo(double.parse(points[i]), double.parse(points[i + 1]));
first = false;
} else {
path.lineTo(double.parse(points[i]), double.parse(points[i + 1]));
}
}
}
// Scalar max is based on 32 bit float since [PathRef] stores values in
// Float32List.
const double kScalarMax = 3.402823466e+38;
const double kScalarMin = -kScalarMax;