content: package com.airbnb.lottie;
  
  import android.graphics.PointF;
  import android.support.annotation.FloatRange;
  
  import org.json.JSONArray;
  import org.json.JSONObject;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  class ShapeData {
    private final List<CubicCurveData> curves = new ArrayList<>();
    private PointF initialPoint;
    private boolean closed;
  
    private ShapeData(PointF initialPoint, boolean closed, List<CubicCurveData> curves) {
      this.initialPoint = initialPoint;
      this.closed = closed;
      this.curves.addAll(curves);
    }
  
    ShapeData() {
    }
  
    private void setInitialPoint(float x, float y) {
      if (initialPoint == null) {
        initialPoint = new PointF();
      }
      initialPoint.set(x, y);
    }
  
    PointF getInitialPoint() {
      return initialPoint;
    }
  
    boolean isClosed() {
      return closed;
    }
  
    List<CubicCurveData> getCurves() {
      return curves;
    }
  
    void interpolateBetween(ShapeData shapeData1, ShapeData shapeData2,
        @FloatRange(from = 0f, to = 1f) float percentage) {
      if (initialPoint == null) {
        initialPoint = new PointF();
      }
      closed = shapeData1.isClosed() || shapeData2.isClosed();
  
      if (!curves.isEmpty() && curves.size() != shapeData1.getCurves().size()
          && curves.size() != shapeData2.getCurves().size()) {
        throw new IllegalStateException("Curves must have the same number of control points. This: "
            + getCurves().size()
            + "\tShape 1: " + shapeData1.getCurves().size() + "\tShape 2: "
            + shapeData2.getCurves().size());
      } else if (curves.isEmpty()) {
        for (int i = shapeData1.getCurves().size() - 1; i >= 0; i--) {
          curves.add(new CubicCurveData());
        }
      }
  
      PointF initialPoint1 = shapeData1.getInitialPoint();
      PointF initialPoint2 = shapeData2.getInitialPoint();
  
      setInitialPoint(MiscUtils.lerp(initialPoint1.x, initialPoint2.x, percentage),
          MiscUtils.lerp(initialPoint1.y, initialPoint2.y, percentage));
  
      for (int i = curves.size() - 1; i >= 0; i--) {
        CubicCurveData curve1 = shapeData1.getCurves().get(i);
        CubicCurveData curve2 = shapeData2.getCurves().get(i);
  
        PointF cp11 = curve1.getControlPoint1();
        PointF cp21 = curve1.getControlPoint2();
        PointF vertex1 = curve1.getVertex();
  
        PointF cp12 = curve2.getControlPoint1();
        PointF cp22 = curve2.getControlPoint2();
        PointF vertex2 = curve2.getVertex();
  
        curves.get(i).setControlPoint1(
            MiscUtils.lerp(cp11.x, cp12.x, percentage), MiscUtils.lerp(cp11.y, cp12.y,
                percentage));
        curves.get(i).setControlPoint2(
            MiscUtils.lerp(cp21.x, cp22.x, percentage), MiscUtils.lerp(cp21.y, cp22.y,
                percentage));
        curves.get(i).setVertex(
            MiscUtils.lerp(vertex1.x, vertex2.x, percentage), MiscUtils.lerp(vertex1.y, vertex2.y,
                percentage));
      }
    }
  
    @Override public String toString() {
      return "ShapeData{" + "numCurves=" + curves.size() +
          "closed=" + closed +
          '}';
    }
  
    static class Factory implements AnimatableValue.Factory<ShapeData> {
      static final ShapeData.Factory INSTANCE = new Factory();
  
      private Factory() {
      }
  
      @Override public ShapeData valueFromObject(Object object, float scale) {
        JSONObject pointsData = null;
        if (object instanceof JSONArray) {
          Object firstObject = ((JSONArray) object).opt(0);
          if (firstObject instanceof JSONObject && ((JSONObject) firstObject).has("v")) {
            pointsData = (JSONObject) firstObject;
          }
        } else if (object instanceof JSONObject && ((JSONObject) object).has("v")) {
          pointsData = (JSONObject) object;
        }
  
        if (pointsData == null) {
          return null;
        }
  
        JSONArray pointsArray = pointsData.optJSONArray("v");
        JSONArray inTangents = pointsData.optJSONArray("i");
        JSONArray outTangents = pointsData.optJSONArray("o");
        boolean closed = pointsData.optBoolean("c", false);
  
        if (pointsArray == null || inTangents == null || outTangents == null ||
            pointsArray.length() != inTangents.length() ||
            pointsArray.length() != outTangents.length()) {
          throw new IllegalStateException(
              "Unable to process points array or tangents. " + pointsData);
        } else if (pointsArray.length() == 0) {
          return new ShapeData(new PointF(), false, Collections.<CubicCurveData>emptyList());
        }
  
        int length = pointsArray.length();
        PointF vertex = vertexAtIndex(0, pointsArray);
        vertex.x *= scale;
        vertex.y *= scale;
        PointF initialPoint = vertex;
        List<CubicCurveData> curves = new ArrayList<>(length);
  
        for (int i = 1; i < length; i++) {
          vertex = vertexAtIndex(i, pointsArray);
          PointF previousVertex = vertexAtIndex(i - 1, pointsArray);
          PointF cp1 = vertexAtIndex(i - 1, outTangents);
          PointF cp2 = vertexAtIndex(i, inTangents);
          PointF shapeCp1 = MiscUtils.addPoints(previousVertex, cp1);
          PointF shapeCp2 = MiscUtils.addPoints(vertex, cp2);
  
          shapeCp1.x *= scale;
          shapeCp1.y *= scale;
          shapeCp2.x *= scale;
          shapeCp2.y *= scale;
          vertex.x *= scale;
          vertex.y *= scale;
  
          curves.add(new CubicCurveData(shapeCp1, shapeCp2, vertex));
        }
  
        if (closed) {
          vertex = vertexAtIndex(0, pointsArray);
          PointF previousVertex = vertexAtIndex(length - 1, pointsArray);
          PointF cp1 = vertexAtIndex(length - 1, outTangents);
          PointF cp2 = vertexAtIndex(0, inTangents);
  
          PointF shapeCp1 = MiscUtils.addPoints(previousVertex, cp1);
          PointF shapeCp2 = MiscUtils.addPoints(vertex, cp2);
  
          if (scale != 1f) {
            shapeCp1.x *= scale;
            shapeCp1.y *= scale;
            shapeCp2.x *= scale;
            shapeCp2.y *= scale;
            vertex.x *= scale;
            vertex.y *= scale;
          }
  
          curves.add(new CubicCurveData(shapeCp1, shapeCp2, vertex));
        }
        return new ShapeData(initialPoint, closed, curves);
      }
  
      private static PointF vertexAtIndex(int idx, JSONArray points) {
        if (idx >= points.length()) {
          throw new IllegalArgumentException(
              "Invalid index " + idx + ". There are only " + points.length() + " points.");
        }
  
        JSONArray pointArray = points.optJSONArray(idx);
        Object x = pointArray.opt(0);
        Object y = pointArray.opt(1);
        return new PointF(
            x instanceof Double ? new Float((Double) x) : (int) x,
            y instanceof Double ? new Float((Double) y) : (int) y);
      }
    }
  }
  