| // 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. | |
| import 'dart:typed_data' show Float64List; | |
| import 'dart:ui'; | |
| import 'package:test/test.dart'; | |
| void main() { | |
| test('path getBounds', () { | |
| const Rect r = Rect.fromLTRB(1.0, 3.0, 5.0, 7.0); | |
| final Path p = Path()..addRect(r); | |
| expect(p.getBounds(), equals(r)); | |
| p.lineTo(20.0, 15.0); | |
| expect(p.getBounds(), equals(const Rect.fromLTRB(1.0, 3.0, 20.0, 15.0))); | |
| }); | |
| test('path combine rect', () { | |
| final Rect c1 = Rect.fromCircle(center: const Offset(10.0, 10.0), radius: 10.0); | |
| final Rect c2 = Rect.fromCircle(center: const Offset(5.0, 5.0), radius: 10.0); | |
| final Rect c1UnionC2 = c1.expandToInclude(c2); | |
| final Rect c1IntersectC2 = c1.intersect(c2); | |
| final Path pathCircle1 = Path()..addRect(c1); | |
| final Path pathCircle2 = Path()..addRect(c2); | |
| final Path difference = Path.combine(PathOperation.difference, pathCircle1, pathCircle2); | |
| expect(difference.getBounds(), equals(c1)); | |
| final Path reverseDifference = Path.combine(PathOperation.reverseDifference, pathCircle1, pathCircle2); | |
| expect(reverseDifference.getBounds(), equals(c2)); | |
| final Path union = Path.combine(PathOperation.union, pathCircle1, pathCircle2); | |
| expect(union.getBounds(), equals(c1UnionC2)); | |
| final Path intersect = Path.combine(PathOperation.intersect, pathCircle1, pathCircle2); | |
| expect(intersect.getBounds(), equals(c1IntersectC2)); | |
| // the bounds on this will be the same as union - but would draw a missing inside piece. | |
| final Path xor = Path.combine(PathOperation.xor, pathCircle1, pathCircle2); | |
| expect(xor.getBounds(), equals(c1UnionC2)); | |
| }); | |
| test('path combine oval', () { | |
| final Rect c1 = Rect.fromCircle(center: const Offset(10.0, 10.0), radius: 10.0); | |
| final Rect c2 = Rect.fromCircle(center: const Offset(5.0, 5.0), radius: 10.0); | |
| final Rect c1UnionC2 = c1.expandToInclude(c2); | |
| final Rect c1IntersectC2 = c1.intersect(c2); | |
| final Path pathCircle1 = Path()..addOval(c1); | |
| final Path pathCircle2 = Path()..addOval(c2); | |
| final Path difference = Path.combine(PathOperation.difference, pathCircle1, pathCircle2); | |
| expect(difference.getBounds().top, closeTo(0.88, 0.01)); | |
| final Path reverseDifference = Path.combine(PathOperation.reverseDifference, pathCircle1, pathCircle2); | |
| expect(reverseDifference.getBounds().right, closeTo(14.11, 0.01)); | |
| final Path union = Path.combine(PathOperation.union, pathCircle1, pathCircle2); | |
| expect(union.getBounds(), equals(c1UnionC2)); | |
| final Path intersect = Path.combine(PathOperation.intersect, pathCircle1, pathCircle2); | |
| expect(intersect.getBounds(), equals(c1IntersectC2)); | |
| // the bounds on this will be the same as union - but would draw a missing inside piece. | |
| final Path xor = Path.combine(PathOperation.xor, pathCircle1, pathCircle2); | |
| expect(xor.getBounds(), equals(c1UnionC2)); | |
| }); | |
| test('path clone', () { | |
| final Path p1 = Path()..lineTo(20.0, 20.0); | |
| final Path p2 = Path.from(p1); | |
| expect(p1.getBounds(), equals(p2.getBounds())); | |
| p1.lineTo(10.0, 30.0); | |
| expect(p1.getBounds().bottom, equals(p2.getBounds().bottom + 10)); | |
| }); | |
| test('shift tests', () { | |
| const Rect bounds = Rect.fromLTRB(0.0, 0.0, 10.0, 10.0); | |
| final Path p = Path()..addRect(bounds); | |
| expect(p.getBounds(), equals(bounds)); | |
| final Path shifted = p.shift(const Offset(10, 10)); | |
| expect(shifted.getBounds(), equals(const Rect.fromLTRB(10, 10, 20, 20))); | |
| }); | |
| test('transformation tests', () { | |
| const Rect bounds = Rect.fromLTRB(0.0, 0.0, 10.0, 10.0); | |
| final Path p = Path()..addRect(bounds); | |
| final Float64List scaleMatrix = Float64List.fromList(<double>[ | |
| 2.5, 0.0, 0.0, 0.0, // first col | |
| 0.0, 0.5, 0.0, 0.0, // second col | |
| 0.0, 0.0, 1.0, 0.0, // third col | |
| 0.0, 0.0, 0.0, 1.0, // fourth col | |
| ]); | |
| expect(p.getBounds(), equals(bounds)); | |
| final Path pTransformed = p.transform(scaleMatrix); | |
| expect(pTransformed.getBounds(), | |
| equals(const Rect.fromLTRB(0.0, 0.0, 10 * 2.5, 10 * 0.5))); | |
| final Path p2 = Path()..lineTo(10.0, 10.0); | |
| p.addPath(p2, const Offset(10.0, 10.0)); | |
| expect(p.getBounds(), equals(const Rect.fromLTRB(0.0, 0.0, 20.0, 20.0))); | |
| p.addPath(p2, const Offset(20.0, 20.0), matrix4: scaleMatrix); | |
| expect(p.getBounds(), | |
| equals(const Rect.fromLTRB(0.0, 0.0, 20 + (10 * 2.5), 20 + (10 * .5)))); | |
| p.extendWithPath(p2, Offset.zero); | |
| expect(p.getBounds(), equals(const Rect.fromLTRB(0.0, 0.0, 45.0, 25.0))); | |
| p.extendWithPath(p2, const Offset(45.0, 25.0), matrix4: scaleMatrix); | |
| expect(p.getBounds(), equals(const Rect.fromLTRB(0.0, 0.0, 70.0, 30.0))); | |
| }); | |
| test('path metrics tests', () { | |
| final Path simpleHorizontalLine = Path()..lineTo(10.0, 0.0); | |
| // basic tests on horizontal line | |
| final PathMetrics simpleHorizontalMetrics = simpleHorizontalLine.computeMetrics(); | |
| expect(() => simpleHorizontalMetrics.iterator.current, throwsRangeError); | |
| expect(simpleHorizontalMetrics.iterator.moveNext(), isTrue); | |
| expect(simpleHorizontalMetrics.iterator.current, isNotNull); | |
| expect(simpleHorizontalMetrics.iterator.current.length, equals(10.0)); | |
| expect(simpleHorizontalMetrics.iterator.current.isClosed, isFalse); | |
| final Path simpleExtract = simpleHorizontalMetrics.iterator.current.extractPath(1.0, 9.0); | |
| expect(simpleExtract.getBounds(), equals(const Rect.fromLTRB(1.0, 0.0, 9.0, 0.0))); | |
| final Tangent posTan = simpleHorizontalMetrics.iterator.current.getTangentForOffset(1.0)!; | |
| expect(posTan.position, equals(const Offset(1.0, 0.0))); | |
| expect(posTan.angle, equals(0.0)); | |
| expect(simpleHorizontalMetrics.iterator.moveNext(), isFalse); | |
| expect(() => simpleHorizontalMetrics.iterator.current, throwsRangeError); | |
| // test with forceClosed | |
| final PathMetrics simpleMetricsClosed = simpleHorizontalLine.computeMetrics(forceClosed: true); | |
| expect(() => simpleHorizontalMetrics.iterator.current, throwsRangeError); | |
| expect(simpleMetricsClosed.iterator.moveNext(), isTrue); | |
| expect(simpleMetricsClosed.iterator.current, isNotNull); | |
| expect(simpleMetricsClosed.iterator.current.length, equals(20.0)); // because we forced close | |
| expect(simpleMetricsClosed.iterator.current.isClosed, isTrue); | |
| final Path simpleExtract2 = simpleMetricsClosed.iterator.current.extractPath(1.0, 9.0); | |
| expect(simpleExtract2.getBounds(), equals(const Rect.fromLTRB(1.0, 0.0, 9.0, 0.0))); | |
| expect(simpleMetricsClosed.iterator.moveNext(), isFalse); | |
| // test getTangentForOffset with vertical line | |
| final Path simpleVerticalLine = Path()..lineTo(0.0, 10.0); | |
| final PathMetrics simpleMetricsVertical = simpleVerticalLine.computeMetrics()..iterator.moveNext(); | |
| final Tangent posTanVertical = simpleMetricsVertical.iterator.current.getTangentForOffset(5.0)!; | |
| expect(posTanVertical.position, equals(const Offset(0.0, 5.0))); | |
| expect(posTanVertical.angle, closeTo(-1.5708, .0001)); // 90 degrees | |
| // test getTangentForOffset with diagonal line | |
| final Path simpleDiagonalLine = Path()..lineTo(10.0, 10.0); | |
| final PathMetrics simpleMetricsDiagonal = simpleDiagonalLine.computeMetrics()..iterator.moveNext(); | |
| final double midPoint = simpleMetricsDiagonal.iterator.current.length / 2; | |
| final Tangent posTanDiagonal = simpleMetricsDiagonal.iterator.current.getTangentForOffset(midPoint)!; | |
| expect(posTanDiagonal.position, equals(const Offset(5.0, 5.0))); | |
| expect(posTanDiagonal.angle, closeTo(-0.7853981633974483, .00001)); // ~45 degrees | |
| // test a multi-contour path | |
| final Path multiContour = Path() | |
| ..lineTo(0.0, 10.0) | |
| ..moveTo(10.0, 10.0) | |
| ..lineTo(10.0, 15.0); | |
| final PathMetrics multiContourMetric = multiContour.computeMetrics(); | |
| expect(() => multiContourMetric.iterator.current, throwsRangeError); | |
| expect(multiContourMetric.iterator.moveNext(), isTrue); | |
| expect(multiContourMetric.iterator.current, isNotNull); | |
| expect(multiContourMetric.iterator.current.length, equals(10.0)); | |
| expect(multiContourMetric.iterator.moveNext(), isTrue); | |
| expect(multiContourMetric.iterator.current, isNotNull); | |
| expect(multiContourMetric.iterator.current.length, equals(5.0)); | |
| expect(multiContourMetric.iterator.moveNext(), isFalse); | |
| expect(() => multiContourMetric.iterator.current, throwsRangeError); | |
| }); | |
| test('PathMetrics can remember lengths and isClosed', () { | |
| final Path path = Path()..lineTo(0, 10) | |
| ..close() | |
| ..moveTo(0, 15) | |
| ..lineTo(10, 15); | |
| final List<PathMetric> metrics = path.computeMetrics().toList(); | |
| expect(metrics.length, 2); | |
| expect(metrics[0].length, 20); | |
| expect(metrics[0].isClosed, true); | |
| expect(metrics[0].getTangentForOffset(4.0)!.vector, const Offset(0.0, 1.0)); | |
| expect(metrics[0].extractPath(4.0, 10.0).computeMetrics().first.length, 6.0); | |
| expect(metrics[1].length, 10); | |
| expect(metrics[1].isClosed, false); | |
| expect(metrics[1].getTangentForOffset(4.0)!.vector, const Offset(1.0, 0.0)); | |
| expect(metrics[1].extractPath(4.0, 6.0).computeMetrics().first.length, 2.0); | |
| }); | |
| test('PathMetrics on a mutated path', () { | |
| final Path path = Path()..lineTo(0, 10); | |
| final PathMetrics metrics = path.computeMetrics(); | |
| final PathMetric firstMetric = metrics.first; | |
| // We've consumed the iterator. | |
| expect(metrics, isEmpty); | |
| expect(firstMetric.length, 10); | |
| expect(firstMetric.isClosed, false); | |
| expect(firstMetric.getTangentForOffset(4.0)!.vector, const Offset(0.0, 1.0)); | |
| expect(firstMetric.extractPath(4.0, 10.0).computeMetrics().first.length, 6.0); | |
| path..lineTo(10, 10)..lineTo(10, 0)..close(); | |
| // mutating the path shouldn't have added anything to the iterator. | |
| expect(metrics, isEmpty); | |
| expect(firstMetric.length, 10); | |
| expect(firstMetric.isClosed, false); | |
| expect(firstMetric.getTangentForOffset(4.0)!.vector, const Offset(0.0, 1.0)); | |
| expect(firstMetric.extractPath(4.0, 10.0).computeMetrics().first.length, 6.0); | |
| // getting a new iterator should update us. | |
| final PathMetrics newMetrics = path.computeMetrics(); | |
| final PathMetric newFirstMetric = newMetrics.first; | |
| expect(newMetrics, isEmpty); | |
| expect(newFirstMetric.length, 40); | |
| expect(newFirstMetric.isClosed, true); | |
| expect(newFirstMetric.getTangentForOffset(4.0)!.vector, const Offset(0.0, 1.0)); | |
| expect(newFirstMetric.extractPath(4.0, 10.0).computeMetrics().first.length, 6.0); | |
| }); | |
| test('PathMetrics on a mutated path', () { | |
| final Path path = Path() | |
| ..lineTo(0, 30) | |
| ..lineTo(40, 30) | |
| ..moveTo(100, 0) | |
| ..lineTo(100, 30) | |
| ..lineTo(140, 30) | |
| ..close(); | |
| final PathMetrics metrics = path.computeMetrics(); | |
| expect(metrics.toString(), | |
| '(PathMetric(length: 70.0, isClosed: false, contourIndex: 0), ' | |
| 'PathMetric(length: 120.0, isClosed: true, contourIndex: 1))', | |
| ); | |
| }); | |
| } |