| // 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'; |
| |
| import 'package:ui/ui.dart' as ui; |
| |
| import 'path_ref.dart'; |
| import 'path_utils.dart'; |
| |
| // Iterates through path including generating closing segments. |
| class PathIterator { |
| PathIterator(this.pathRef, bool forceClose) |
| : _forceClose = forceClose, |
| _verbCount = pathRef.countVerbs() { |
| _pointIndex = 0; |
| if (!pathRef.isFinite) { |
| // Don't allow iteration through non-finite points, prepare to return |
| // done verb. |
| _verbIndex = pathRef.countVerbs(); |
| } |
| } |
| |
| final PathRef pathRef; |
| final bool _forceClose; |
| final int _verbCount; |
| |
| bool _needClose = false; |
| int _segmentState = SPathSegmentState.kEmptyContour; |
| int _conicWeightIndex = -1; |
| double _lastPointX = 0; |
| double _lastPointY = 0; |
| double _moveToX = 0; |
| double _moveToY = 0; |
| int _verbIndex = 0; |
| int _pointIndex = 0; |
| |
| int get pathVerbIndex => _verbIndex; |
| int get conicWeightIndex => _conicWeightIndex; |
| |
| /// Maximum buffer size required for points in [next] calls. |
| static const int kMaxBufferSize = 8; |
| |
| /// Returns true if first contour on path is closed. |
| bool isClosedContour() { |
| if (_verbCount == 0 || _verbIndex == _verbCount) { |
| return false; |
| } |
| if (_forceClose) { |
| return true; |
| } |
| int verbIndex = 0; |
| // Skip starting moveTo. |
| if (pathRef.atVerb(verbIndex) == SPath.kMoveVerb) { |
| ++verbIndex; |
| } |
| while (verbIndex < _verbCount) { |
| final int verb = pathRef.atVerb(verbIndex++); |
| if (SPath.kMoveVerb == verb) { |
| break; |
| } |
| if (SPath.kCloseVerb == verb) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| int _autoClose(Float32List outPts) { |
| if (_lastPointX != _moveToX || _lastPointY != _moveToY) { |
| // Handle special case where comparison above will return true for |
| // NaN != NaN although it should be false. |
| if (_lastPointX.isNaN || |
| _lastPointY.isNaN || |
| _moveToX.isNaN || |
| _moveToY.isNaN) { |
| return SPath.kCloseVerb; |
| } |
| outPts[0] = _lastPointX; |
| outPts[1] = _lastPointY; |
| outPts[2] = _moveToX; |
| outPts[3] = _moveToY; |
| _lastPointX = _moveToX; |
| _lastPointY = _moveToY; |
| return SPath.kLineVerb; |
| } else { |
| outPts[0] = _moveToX; |
| outPts[1] = _moveToY; |
| return SPath.kCloseVerb; |
| } |
| } |
| |
| // Returns true if caller should use moveTo, false if last point of |
| // previous primitive. |
| ui.Offset _constructMoveTo() { |
| if (_segmentState == SPathSegmentState.kAfterMove) { |
| // Set the first return point to move point. |
| _segmentState = SPathSegmentState.kAfterPrimitive; |
| return ui.Offset(_moveToX, _moveToY); |
| } |
| return ui.Offset( |
| pathRef.points[_pointIndex - 2], pathRef.points[_pointIndex - 1]); |
| } |
| |
| int peek() { |
| if (_verbIndex < pathRef.countVerbs()) { |
| return pathRef.atVerb(_verbIndex); |
| } |
| if (_needClose && _segmentState == SPathSegmentState.kAfterPrimitive) { |
| return (_lastPointX != _moveToX || _lastPointY != _moveToY) |
| ? SPath.kLineVerb |
| : SPath.kCloseVerb; |
| } |
| return SPath.kDoneVerb; |
| } |
| |
| // Returns next verb and reads associated points into [outPts]. |
| int next(Float32List outPts) { |
| if (_verbIndex == pathRef.countVerbs()) { |
| // Close the curve if requested and if there is some curve to close |
| if (_needClose && _segmentState == SPathSegmentState.kAfterPrimitive) { |
| if (SPath.kLineVerb == _autoClose(outPts)) { |
| return SPath.kLineVerb; |
| } |
| _needClose = false; |
| return SPath.kCloseVerb; |
| } |
| return SPath.kDoneVerb; |
| } |
| int verb = pathRef.atVerb(_verbIndex++); |
| switch (verb) { |
| case SPath.kMoveVerb: |
| if (_needClose) { |
| // Move back one verb. |
| _verbIndex--; |
| final int autoVerb = _autoClose(outPts); |
| if (autoVerb == SPath.kCloseVerb) { |
| _needClose = false; |
| } |
| return autoVerb; |
| } |
| if (_verbIndex == _verbCount) { |
| return SPath.kDoneVerb; |
| } |
| final double offsetX = pathRef.points[_pointIndex++]; |
| final double offsetY = pathRef.points[_pointIndex++]; |
| _moveToX = offsetX; |
| _moveToY = offsetY; |
| outPts[0] = offsetX; |
| outPts[1] = offsetY; |
| _segmentState = SPathSegmentState.kAfterMove; |
| _lastPointX = _moveToX; |
| _lastPointY = _moveToY; |
| _needClose = _forceClose; |
| break; |
| case SPath.kLineVerb: |
| final ui.Offset start = _constructMoveTo(); |
| final double offsetX = pathRef.points[_pointIndex++]; |
| final double offsetY = pathRef.points[_pointIndex++]; |
| outPts[0] = start.dx; |
| outPts[1] = start.dy; |
| outPts[2] = offsetX; |
| outPts[3] = offsetY; |
| _lastPointX = offsetX; |
| _lastPointY = offsetY; |
| break; |
| case SPath.kConicVerb: |
| _conicWeightIndex++; |
| final ui.Offset start = _constructMoveTo(); |
| outPts[0] = start.dx; |
| outPts[1] = start.dy; |
| outPts[2] = pathRef.points[_pointIndex++]; |
| outPts[3] = pathRef.points[_pointIndex++]; |
| _lastPointX = outPts[4] = pathRef.points[_pointIndex++]; |
| _lastPointY = outPts[5] = pathRef.points[_pointIndex++]; |
| break; |
| case SPath.kQuadVerb: |
| final ui.Offset start = _constructMoveTo(); |
| outPts[0] = start.dx; |
| outPts[1] = start.dy; |
| outPts[2] = pathRef.points[_pointIndex++]; |
| outPts[3] = pathRef.points[_pointIndex++]; |
| _lastPointX = outPts[4] = pathRef.points[_pointIndex++]; |
| _lastPointY = outPts[5] = pathRef.points[_pointIndex++]; |
| break; |
| case SPath.kCubicVerb: |
| final ui.Offset start = _constructMoveTo(); |
| outPts[0] = start.dx; |
| outPts[1] = start.dy; |
| outPts[2] = pathRef.points[_pointIndex++]; |
| outPts[3] = pathRef.points[_pointIndex++]; |
| outPts[4] = pathRef.points[_pointIndex++]; |
| outPts[5] = pathRef.points[_pointIndex++]; |
| _lastPointX = outPts[6] = pathRef.points[_pointIndex++]; |
| _lastPointY = outPts[7] = pathRef.points[_pointIndex++]; |
| break; |
| case SPath.kCloseVerb: |
| verb = _autoClose(outPts); |
| if (verb == SPath.kLineVerb) { |
| // Move back one verb since we constructed line for this close verb. |
| _verbIndex--; |
| } else { |
| _needClose = false; |
| _segmentState = SPathSegmentState.kEmptyContour; |
| } |
| _lastPointX = _moveToX; |
| _lastPointY = _moveToY; |
| break; |
| case SPath.kDoneVerb: |
| assert(_verbIndex == pathRef.countVerbs()); |
| break; |
| default: |
| throw FormatException('Unsupport Path verb $verb'); |
| } |
| return verb; |
| } |
| |
| double get conicWeight => pathRef.atWeight(_conicWeightIndex); |
| } |