blob: 90793a95004f066bfdd8bf79dadbeabf91f63bfb [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.
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);
}