| // 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:litetest/litetest.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))', |
| ); |
| }); |
| } |