private void updatePath()

in ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java [475:876]


  private void updatePath() {
    if (!mNeedUpdatePathForBorderRadius) {
      return;
    }

    mNeedUpdatePathForBorderRadius = false;

    if (mInnerClipPathForBorderRadius == null) {
      mInnerClipPathForBorderRadius = new Path();
    }

    if (mOuterClipPathForBorderRadius == null) {
      mOuterClipPathForBorderRadius = new Path();
    }

    if (mPathForBorderRadiusOutline == null) {
      mPathForBorderRadiusOutline = new Path();
    }

    if (mCenterDrawPath == null) {
      mCenterDrawPath = new Path();
    }

    if (mInnerClipTempRectForBorderRadius == null) {
      mInnerClipTempRectForBorderRadius = new RectF();
    }

    if (mOuterClipTempRectForBorderRadius == null) {
      mOuterClipTempRectForBorderRadius = new RectF();
    }

    if (mTempRectForBorderRadiusOutline == null) {
      mTempRectForBorderRadiusOutline = new RectF();
    }

    if (mTempRectForCenterDrawPath == null) {
      mTempRectForCenterDrawPath = new RectF();
    }

    mInnerClipPathForBorderRadius.reset();
    mOuterClipPathForBorderRadius.reset();
    mPathForBorderRadiusOutline.reset();
    mCenterDrawPath.reset();

    mInnerClipTempRectForBorderRadius.set(getBounds());
    mOuterClipTempRectForBorderRadius.set(getBounds());
    mTempRectForBorderRadiusOutline.set(getBounds());
    mTempRectForCenterDrawPath.set(getBounds());

    final RectF borderWidth = getDirectionAwareBorderInsets();

    mInnerClipTempRectForBorderRadius.top += borderWidth.top;
    mInnerClipTempRectForBorderRadius.bottom -= borderWidth.bottom;
    mInnerClipTempRectForBorderRadius.left += borderWidth.left;
    mInnerClipTempRectForBorderRadius.right -= borderWidth.right;

    mTempRectForCenterDrawPath.top += borderWidth.top * 0.5f;
    mTempRectForCenterDrawPath.bottom -= borderWidth.bottom * 0.5f;
    mTempRectForCenterDrawPath.left += borderWidth.left * 0.5f;
    mTempRectForCenterDrawPath.right -= borderWidth.right * 0.5f;

    final float borderRadius = getFullBorderRadius();
    float topLeftRadius = getBorderRadiusOrDefaultTo(borderRadius, BorderRadiusLocation.TOP_LEFT);
    float topRightRadius = getBorderRadiusOrDefaultTo(borderRadius, BorderRadiusLocation.TOP_RIGHT);
    float bottomLeftRadius =
        getBorderRadiusOrDefaultTo(borderRadius, BorderRadiusLocation.BOTTOM_LEFT);
    float bottomRightRadius =
        getBorderRadiusOrDefaultTo(borderRadius, BorderRadiusLocation.BOTTOM_RIGHT);

    final boolean isRTL = getResolvedLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
    float topStartRadius = getBorderRadius(BorderRadiusLocation.TOP_START);
    float topEndRadius = getBorderRadius(BorderRadiusLocation.TOP_END);
    float bottomStartRadius = getBorderRadius(BorderRadiusLocation.BOTTOM_START);
    float bottomEndRadius = getBorderRadius(BorderRadiusLocation.BOTTOM_END);

    if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(mContext)) {
      if (YogaConstants.isUndefined(topStartRadius)) {
        topStartRadius = topLeftRadius;
      }

      if (YogaConstants.isUndefined(topEndRadius)) {
        topEndRadius = topRightRadius;
      }

      if (YogaConstants.isUndefined(bottomStartRadius)) {
        bottomStartRadius = bottomLeftRadius;
      }

      if (YogaConstants.isUndefined(bottomEndRadius)) {
        bottomEndRadius = bottomRightRadius;
      }

      final float directionAwareTopLeftRadius = isRTL ? topEndRadius : topStartRadius;
      final float directionAwareTopRightRadius = isRTL ? topStartRadius : topEndRadius;
      final float directionAwareBottomLeftRadius = isRTL ? bottomEndRadius : bottomStartRadius;
      final float directionAwareBottomRightRadius = isRTL ? bottomStartRadius : bottomEndRadius;

      topLeftRadius = directionAwareTopLeftRadius;
      topRightRadius = directionAwareTopRightRadius;
      bottomLeftRadius = directionAwareBottomLeftRadius;
      bottomRightRadius = directionAwareBottomRightRadius;
    } else {
      final float directionAwareTopLeftRadius = isRTL ? topEndRadius : topStartRadius;
      final float directionAwareTopRightRadius = isRTL ? topStartRadius : topEndRadius;
      final float directionAwareBottomLeftRadius = isRTL ? bottomEndRadius : bottomStartRadius;
      final float directionAwareBottomRightRadius = isRTL ? bottomStartRadius : bottomEndRadius;

      if (!YogaConstants.isUndefined(directionAwareTopLeftRadius)) {
        topLeftRadius = directionAwareTopLeftRadius;
      }

      if (!YogaConstants.isUndefined(directionAwareTopRightRadius)) {
        topRightRadius = directionAwareTopRightRadius;
      }

      if (!YogaConstants.isUndefined(directionAwareBottomLeftRadius)) {
        bottomLeftRadius = directionAwareBottomLeftRadius;
      }

      if (!YogaConstants.isUndefined(directionAwareBottomRightRadius)) {
        bottomRightRadius = directionAwareBottomRightRadius;
      }
    }

    final float innerTopLeftRadiusX = Math.max(topLeftRadius - borderWidth.left, 0);
    final float innerTopLeftRadiusY = Math.max(topLeftRadius - borderWidth.top, 0);
    final float innerTopRightRadiusX = Math.max(topRightRadius - borderWidth.right, 0);
    final float innerTopRightRadiusY = Math.max(topRightRadius - borderWidth.top, 0);
    final float innerBottomRightRadiusX = Math.max(bottomRightRadius - borderWidth.right, 0);
    final float innerBottomRightRadiusY = Math.max(bottomRightRadius - borderWidth.bottom, 0);
    final float innerBottomLeftRadiusX = Math.max(bottomLeftRadius - borderWidth.left, 0);
    final float innerBottomLeftRadiusY = Math.max(bottomLeftRadius - borderWidth.bottom, 0);

    mInnerClipPathForBorderRadius.addRoundRect(
        mInnerClipTempRectForBorderRadius,
        new float[] {
          innerTopLeftRadiusX,
          innerTopLeftRadiusY,
          innerTopRightRadiusX,
          innerTopRightRadiusY,
          innerBottomRightRadiusX,
          innerBottomRightRadiusY,
          innerBottomLeftRadiusX,
          innerBottomLeftRadiusY,
        },
        Path.Direction.CW);

    mOuterClipPathForBorderRadius.addRoundRect(
        mOuterClipTempRectForBorderRadius,
        new float[] {
          topLeftRadius,
          topLeftRadius,
          topRightRadius,
          topRightRadius,
          bottomRightRadius,
          bottomRightRadius,
          bottomLeftRadius,
          bottomLeftRadius
        },
        Path.Direction.CW);

    float extraRadiusForOutline = 0;

    if (mBorderWidth != null) {
      extraRadiusForOutline = mBorderWidth.get(Spacing.ALL) / 2f;
    }

    mPathForBorderRadiusOutline.addRoundRect(
        mTempRectForBorderRadiusOutline,
        new float[] {
          topLeftRadius + extraRadiusForOutline,
          topLeftRadius + extraRadiusForOutline,
          topRightRadius + extraRadiusForOutline,
          topRightRadius + extraRadiusForOutline,
          bottomRightRadius + extraRadiusForOutline,
          bottomRightRadius + extraRadiusForOutline,
          bottomLeftRadius + extraRadiusForOutline,
          bottomLeftRadius + extraRadiusForOutline
        },
        Path.Direction.CW);

    mCenterDrawPath.addRoundRect(
        mTempRectForCenterDrawPath,
        new float[] {
          Math.max(
              topLeftRadius - borderWidth.left * 0.5f,
              (borderWidth.left > 0.0f) ? (topLeftRadius / borderWidth.left) : 0.0f),
          Math.max(
              topLeftRadius - borderWidth.top * 0.5f,
              (borderWidth.top > 0.0f) ? (topLeftRadius / borderWidth.top) : 0.0f),
          Math.max(
              topRightRadius - borderWidth.right * 0.5f,
              (borderWidth.right > 0.0f) ? (topRightRadius / borderWidth.right) : 0.0f),
          Math.max(
              topRightRadius - borderWidth.top * 0.5f,
              (borderWidth.top > 0.0f) ? (topRightRadius / borderWidth.top) : 0.0f),
          Math.max(
              bottomRightRadius - borderWidth.right * 0.5f,
              (borderWidth.right > 0.0f) ? (bottomRightRadius / borderWidth.right) : 0.0f),
          Math.max(
              bottomRightRadius - borderWidth.bottom * 0.5f,
              (borderWidth.bottom > 0.0f) ? (bottomRightRadius / borderWidth.bottom) : 0.0f),
          Math.max(
              bottomLeftRadius - borderWidth.left * 0.5f,
              (borderWidth.left > 0.0f) ? (bottomLeftRadius / borderWidth.left) : 0.0f),
          Math.max(
              bottomLeftRadius - borderWidth.bottom * 0.5f,
              (borderWidth.bottom > 0.0f) ? (bottomLeftRadius / borderWidth.bottom) : 0.0f)
        },
        Path.Direction.CW);

    /**
     * Rounded Multi-Colored Border Algorithm:
     *
     * <p>Let O (for outer) = (top, left, bottom, right) be the rectangle that represents the size
     * and position of a view V. Since the box-sizing of all React Native views is border-box, any
     * border of V will render inside O.
     *
     * <p>Let BorderWidth = (borderTop, borderLeft, borderBottom, borderRight).
     *
     * <p>Let I (for inner) = O - BorderWidth.
     *
     * <p>Then, remembering that O and I are rectangles and that I is inside O, O - I gives us the
     * border of V. Therefore, we can use canvas.clipPath to draw V's border.
     *
     * <p>canvas.clipPath(O, Region.OP.INTERSECT);
     *
     * <p>canvas.clipPath(I, Region.OP.DIFFERENCE);
     *
     * <p>canvas.drawRect(O, paint);
     *
     * <p>This lets us draw non-rounded single-color borders.
     *
     * <p>To extend this algorithm to rounded single-color borders, we:
     *
     * <p>1. Curve the corners of O by the (border radii of V) using Path#addRoundRect.
     *
     * <p>2. Curve the corners of I by (border radii of V - border widths of V) using
     * Path#addRoundRect.
     *
     * <p>Let O' = curve(O, border radii of V).
     *
     * <p>Let I' = curve(I, border radii of V - border widths of V)
     *
     * <p>The rationale behind this decision is the (first sentence of the) following section in the
     * CSS Backgrounds and Borders Module Level 3:
     * https://www.w3.org/TR/css3-background/#the-border-radius.
     *
     * <p>After both O and I have been curved, we can execute the following lines once again to
     * render curved single-color borders:
     *
     * <p>canvas.clipPath(O, Region.OP.INTERSECT);
     *
     * <p>canvas.clipPath(I, Region.OP.DIFFERENCE);
     *
     * <p>canvas.drawRect(O, paint);
     *
     * <p>To extend this algorithm to rendering multi-colored rounded borders, we render each side
     * of the border as its own quadrilateral. Suppose that we were handling the case where all the
     * border radii are 0. Then, the four quadrilaterals would be:
     *
     * <p>Left: (O.left, O.top), (I.left, I.top), (I.left, I.bottom), (O.left, O.bottom)
     *
     * <p>Top: (O.left, O.top), (I.left, I.top), (I.right, I.top), (O.right, O.top)
     *
     * <p>Right: (O.right, O.top), (I.right, I.top), (I.right, I.bottom), (O.right, O.bottom)
     *
     * <p>Bottom: (O.right, O.bottom), (I.right, I.bottom), (I.left, I.bottom), (O.left, O.bottom)
     *
     * <p>Now, lets consider what happens when we render a rounded border (radii != 0). For the sake
     * of simplicity, let's focus on the top edge of the Left border:
     *
     * <p>Let borderTopLeftRadius = 5. Let borderLeftWidth = 1. Let borderTopWidth = 2.
     *
     * <p>We know that O is curved by the ellipse E_O (a = 5, b = 5). We know that I is curved by
     * the ellipse E_I (a = 5 - 1, b = 5 - 2).
     *
     * <p>Since we have clipping, it should be safe to set the top-left point of the Left
     * quadrilateral's top edge to (O.left, O.top).
     *
     * <p>But, what should the top-right point be?
     *
     * <p>The fact that the border is curved shouldn't change the slope (nor the position) of the
     * line connecting the top-left and top-right points of the Left quadrilateral's top edge.
     * Therefore, The top-right point should lie somewhere on the line L = (1 - a) * (O.left, O.top)
     * + a * (I.left, I.top).
     *
     * <p>a != 0, because then the top-left and top-right points would be the same and
     * borderLeftWidth = 1. a != 1, because then the top-right point would not touch an edge of the
     * ellipse E_I. We want the top-right point to touch an edge of the inner ellipse because the
     * border curves with E_I on the top-left corner of V.
     *
     * <p>Therefore, it must be the case that a > 1. Two natural locations of the top-right point
     * exist: 1. The first intersection of L with E_I. 2. The second intersection of L with E_I.
     *
     * <p>We choose the top-right point of the top edge of the Left quadrilateral to be an arbitrary
     * intersection of L with E_I.
     */
    if (mInnerTopLeftCorner == null) {
      mInnerTopLeftCorner = new PointF();
    }

    /** Compute mInnerTopLeftCorner */
    mInnerTopLeftCorner.x = mInnerClipTempRectForBorderRadius.left;
    mInnerTopLeftCorner.y = mInnerClipTempRectForBorderRadius.top;

    getEllipseIntersectionWithLine(
        // Ellipse Bounds
        mInnerClipTempRectForBorderRadius.left,
        mInnerClipTempRectForBorderRadius.top,
        mInnerClipTempRectForBorderRadius.left + 2 * innerTopLeftRadiusX,
        mInnerClipTempRectForBorderRadius.top + 2 * innerTopLeftRadiusY,

        // Line Start
        mOuterClipTempRectForBorderRadius.left,
        mOuterClipTempRectForBorderRadius.top,

        // Line End
        mInnerClipTempRectForBorderRadius.left,
        mInnerClipTempRectForBorderRadius.top,

        // Result
        mInnerTopLeftCorner);

    /** Compute mInnerBottomLeftCorner */
    if (mInnerBottomLeftCorner == null) {
      mInnerBottomLeftCorner = new PointF();
    }

    mInnerBottomLeftCorner.x = mInnerClipTempRectForBorderRadius.left;
    mInnerBottomLeftCorner.y = mInnerClipTempRectForBorderRadius.bottom;

    getEllipseIntersectionWithLine(
        // Ellipse Bounds
        mInnerClipTempRectForBorderRadius.left,
        mInnerClipTempRectForBorderRadius.bottom - 2 * innerBottomLeftRadiusY,
        mInnerClipTempRectForBorderRadius.left + 2 * innerBottomLeftRadiusX,
        mInnerClipTempRectForBorderRadius.bottom,

        // Line Start
        mOuterClipTempRectForBorderRadius.left,
        mOuterClipTempRectForBorderRadius.bottom,

        // Line End
        mInnerClipTempRectForBorderRadius.left,
        mInnerClipTempRectForBorderRadius.bottom,

        // Result
        mInnerBottomLeftCorner);

    /** Compute mInnerTopRightCorner */
    if (mInnerTopRightCorner == null) {
      mInnerTopRightCorner = new PointF();
    }

    mInnerTopRightCorner.x = mInnerClipTempRectForBorderRadius.right;
    mInnerTopRightCorner.y = mInnerClipTempRectForBorderRadius.top;

    getEllipseIntersectionWithLine(
        // Ellipse Bounds
        mInnerClipTempRectForBorderRadius.right - 2 * innerTopRightRadiusX,
        mInnerClipTempRectForBorderRadius.top,
        mInnerClipTempRectForBorderRadius.right,
        mInnerClipTempRectForBorderRadius.top + 2 * innerTopRightRadiusY,

        // Line Start
        mOuterClipTempRectForBorderRadius.right,
        mOuterClipTempRectForBorderRadius.top,

        // Line End
        mInnerClipTempRectForBorderRadius.right,
        mInnerClipTempRectForBorderRadius.top,

        // Result
        mInnerTopRightCorner);

    /** Compute mInnerBottomRightCorner */
    if (mInnerBottomRightCorner == null) {
      mInnerBottomRightCorner = new PointF();
    }

    mInnerBottomRightCorner.x = mInnerClipTempRectForBorderRadius.right;
    mInnerBottomRightCorner.y = mInnerClipTempRectForBorderRadius.bottom;

    getEllipseIntersectionWithLine(
        // Ellipse Bounds
        mInnerClipTempRectForBorderRadius.right - 2 * innerBottomRightRadiusX,
        mInnerClipTempRectForBorderRadius.bottom - 2 * innerBottomRightRadiusY,
        mInnerClipTempRectForBorderRadius.right,
        mInnerClipTempRectForBorderRadius.bottom,

        // Line Start
        mOuterClipTempRectForBorderRadius.right,
        mOuterClipTempRectForBorderRadius.bottom,

        // Line End
        mInnerClipTempRectForBorderRadius.right,
        mInnerClipTempRectForBorderRadius.bottom,

        // Result
        mInnerBottomRightCorner);
  }