content: /*
   * Copyright (C) 2006 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.graphics;
  
  import android.annotation.CheckResult;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.UnsupportedAppUsage;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.text.TextUtils;
  import android.util.proto.ProtoInputStream;
  import android.util.proto.ProtoOutputStream;
  import android.util.proto.WireTypeMismatchException;
  
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  /**
   * Rect holds four integer coordinates for a rectangle. The rectangle is
   * represented by the coordinates of its 4 edges (left, top, right bottom).
   * These fields can be accessed directly. Use width() and height() to retrieve
   * the rectangle's width and height. Note: most methods do not check to see that
   * the coordinates are sorted correctly (i.e. left <= right and top <= bottom).
   * <p>
   * Note that the right and bottom coordinates are exclusive. This means a Rect
   * being drawn untransformed onto a {@link android.graphics.Canvas} will draw
   * into the column and row described by its left and top coordinates, but not
   * those of its bottom and right.
   */
  public final class Rect implements Parcelable {
      public int left;
      public int top;
      public int right;
      public int bottom;
  
      /**
       * A helper class for flattened rectange pattern recognition. A separate
       * class to avoid an initialization dependency on a regular expression
       * causing Rect to not be initializable with an ahead-of-time compilation
       * scheme.
       */
      private static final class UnflattenHelper {
          private static final Pattern FLATTENED_PATTERN = Pattern.compile(
              "(-?\\d+) (-?\\d+) (-?\\d+) (-?\\d+)");
  
          static Matcher getMatcher(String str) {
              return FLATTENED_PATTERN.matcher(str);
          }
      }
  
      /**
       * Create a new empty Rect. All coordinates are initialized to 0.
       */
      public Rect() {}
  
      /**
       * Create a new rectangle with the specified coordinates. Note: no range
       * checking is performed, so the caller must ensure that left <= right and
       * top <= bottom.
       *
       * @param left   The X coordinate of the left side of the rectangle
       * @param top    The Y coordinate of the top of the rectangle
       * @param right  The X coordinate of the right side of the rectangle
       * @param bottom The Y coordinate of the bottom of the rectangle
       */
      public Rect(int left, int top, int right, int bottom) {
          this.left = left;
          this.top = top;
          this.right = right;
          this.bottom = bottom;
      }
  
      /**
       * Create a new rectangle, initialized with the values in the specified
       * rectangle (which is left unmodified).
       *
       * @param r The rectangle whose coordinates are copied into the new
       *          rectangle.
       */
      public Rect(@Nullable Rect r) {
          if (r == null) {
              left = top = right = bottom = 0;
          } else {
              left = r.left;
              top = r.top;
              right = r.right;
              bottom = r.bottom;
          }
      }
  
      /**
       * @hide
       */
      public Rect(@Nullable Insets r) {
          if (r == null) {
              left = top = right = bottom = 0;
          } else {
              left = r.left;
              top = r.top;
              right = r.right;
              bottom = r.bottom;
          }
      }
  
      /**
       * Returns a copy of {@code r} if {@code r} is not {@code null}, or {@code null} otherwise.
       *
       * @hide
       */
      @Nullable
      public static Rect copyOrNull(@Nullable Rect r) {
          return r == null ? null : new Rect(r);
      }
  
      @Override
      public boolean equals(Object o) {
          if (this == o) return true;
          if (o == null || getClass() != o.getClass()) return false;
  
          Rect r = (Rect) o;
          return left == r.left && top == r.top && right == r.right && bottom == r.bottom;
      }
  
      @Override
      public int hashCode() {
          int result = left;
          result = 31 * result + top;
          result = 31 * result + right;
          result = 31 * result + bottom;
          return result;
      }
  
      @Override
      public String toString() {
          StringBuilder sb = new StringBuilder(32);
          sb.append("Rect("); sb.append(left); sb.append(", ");
          sb.append(top); sb.append(" - "); sb.append(right);
          sb.append(", "); sb.append(bottom); sb.append(")");
          return sb.toString();
      }
  
      /**
       * Return a string representation of the rectangle in a compact form.
       */
      @NonNull
      public String toShortString() {
          return toShortString(new StringBuilder(32));
      }
      
      /**
       * Return a string representation of the rectangle in a compact form.
       * @hide
       */
      @NonNull
      public String toShortString(@NonNull StringBuilder sb) {
          sb.setLength(0);
          sb.append('['); sb.append(left); sb.append(',');
          sb.append(top); sb.append("]["); sb.append(right);
          sb.append(','); sb.append(bottom); sb.append(']');
          return sb.toString();
      }
  
      /**
       * Return a string representation of the rectangle in a well-defined format.
       *
       * <p>You can later recover the Rect from this string through
       * {@link #unflattenFromString(String)}.
       * 
       * @return Returns a new String of the form "left top right bottom"
       */
      @NonNull
      public String flattenToString() {
          StringBuilder sb = new StringBuilder(32);
          // WARNING: Do not change the format of this string, it must be
          // preserved because Rects are saved in this flattened format.
          sb.append(left);
          sb.append(' ');
          sb.append(top);
          sb.append(' ');
          sb.append(right);
          sb.append(' ');
          sb.append(bottom);
          return sb.toString();
      }
  
      /**
       * Returns a Rect from a string of the form returned by {@link #flattenToString},
       * or null if the string is not of that form.
       */
      @Nullable
      public static Rect unflattenFromString(@Nullable String str) {
          if (TextUtils.isEmpty(str)) {
              return null;
          }
  
          Matcher matcher = UnflattenHelper.getMatcher(str);
          if (!matcher.matches()) {
              return null;
          }
          return new Rect(Integer.parseInt(matcher.group(1)),
                  Integer.parseInt(matcher.group(2)),
                  Integer.parseInt(matcher.group(3)),
                  Integer.parseInt(matcher.group(4)));
      }
  
      /**
       * Print short representation to given writer.
       * @hide
       */
      @UnsupportedAppUsage
      public void printShortString(@NonNull PrintWriter pw) {
          pw.print('['); pw.print(left); pw.print(',');
          pw.print(top); pw.print("]["); pw.print(right);
          pw.print(','); pw.print(bottom); pw.print(']');
      }
  
      /**
       * Write to a protocol buffer output stream.
       * Protocol buffer message definition at {@link android.graphics.RectProto}
       *
       * @param protoOutputStream Stream to write the Rect object to.
       * @param fieldId           Field Id of the Rect as defined in the parent message
       * @hide
       */
      public void writeToProto(@NonNull ProtoOutputStream protoOutputStream, long fieldId) {
          final long token = protoOutputStream.start(fieldId);
          protoOutputStream.write(RectProto.LEFT, left);
          protoOutputStream.write(RectProto.TOP, top);
          protoOutputStream.write(RectProto.RIGHT, right);
          protoOutputStream.write(RectProto.BOTTOM, bottom);
          protoOutputStream.end(token);
      }
  
      /**
       * Read from a protocol buffer input stream.
       * Protocol buffer message definition at {@link android.graphics.RectProto}
       *
       * @param proto     Stream to read the Rect object from.
       * @param fieldId   Field Id of the Rect as defined in the parent message
       * @hide
       */
      public void readFromProto(@NonNull ProtoInputStream proto, long fieldId) throws IOException,
              WireTypeMismatchException {
          final long token = proto.start(fieldId);
          try {
              while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                  switch (proto.getFieldNumber()) {
                      case (int) RectProto.LEFT:
                          left = proto.readInt(RectProto.LEFT);
                          break;
                      case (int) RectProto.TOP:
                          top = proto.readInt(RectProto.TOP);
                          break;
                      case (int) RectProto.RIGHT:
                          right = proto.readInt(RectProto.RIGHT);
                          break;
                      case (int) RectProto.BOTTOM:
                          bottom = proto.readInt(RectProto.BOTTOM);
                          break;
                  }
              }
          } finally {
              // Let caller handle any exceptions
              proto.end(token);
          }
      }
  
      /**
       * Returns true if the rectangle is empty (left >= right or top >= bottom)
       */
      public final boolean isEmpty() {
          return left >= right || top >= bottom;
      }
  
      /**
       * @return the rectangle's width. This does not check for a valid rectangle
       * (i.e. left <= right) so the result may be negative.
       */
      public final int width() {
          return right - left;
      }
  
      /**
       * @return the rectangle's height. This does not check for a valid rectangle
       * (i.e. top <= bottom) so the result may be negative.
       */
      public final int height() {
          return bottom - top;
      }
      
      /**
       * @return the horizontal center of the rectangle. If the computed value
       *         is fractional, this method returns the largest integer that is
       *         less than the computed value.
       */
      public final int centerX() {
          return (left + right) >> 1;
      }
      
      /**
       * @return the vertical center of the rectangle. If the computed value
       *         is fractional, this method returns the largest integer that is
       *         less than the computed value.
       */
      public final int centerY() {
          return (top + bottom) >> 1;
      }
      
      /**
       * @return the exact horizontal center of the rectangle as a float.
       */
      public final float exactCenterX() {
          return (left + right) * 0.5f;
      }
      
      /**
       * @return the exact vertical center of the rectangle as a float.
       */
      public final float exactCenterY() {
          return (top + bottom) * 0.5f;
      }
  
      /**
       * Set the rectangle to (0,0,0,0)
       */
      public void setEmpty() {
          left = right = top = bottom = 0;
      }
  
      /**
       * Set the rectangle's coordinates to the specified values. Note: no range
       * checking is performed, so it is up to the caller to ensure that
       * left <= right and top <= bottom.
       *
       * @param left   The X coordinate of the left side of the rectangle
       * @param top    The Y coordinate of the top of the rectangle
       * @param right  The X coordinate of the right side of the rectangle
       * @param bottom The Y coordinate of the bottom of the rectangle
       */
      public void set(int left, int top, int right, int bottom) {
          this.left = left;
          this.top = top;
          this.right = right;
          this.bottom = bottom;
      }
  
      /**
       * Copy the coordinates from src into this rectangle.
       *
       * @param src The rectangle whose coordinates are copied into this
       *           rectangle.
       */
      public void set(@NonNull Rect src) {
          this.left = src.left;
          this.top = src.top;
          this.right = src.right;
          this.bottom = src.bottom;
      }
  
      /**
       * Offset the rectangle by adding dx to its left and right coordinates, and
       * adding dy to its top and bottom coordinates.
       *
       * @param dx The amount to add to the rectangle's left and right coordinates
       * @param dy The amount to add to the rectangle's top and bottom coordinates
       */
      public void offset(int dx, int dy) {
          left += dx;
          top += dy;
          right += dx;
          bottom += dy;
      }
  
      /**
       * Offset the rectangle to a specific (left, top) position,
       * keeping its width and height the same.
       *
       * @param newLeft   The new "left" coordinate for the rectangle
       * @param newTop    The new "top" coordinate for the rectangle
       */
      public void offsetTo(int newLeft, int newTop) {
          right += newLeft - left;
          bottom += newTop - top;
          left = newLeft;
          top = newTop;
      }
  
      /**
       * Inset the rectangle by (dx,dy). If dx is positive, then the sides are
       * moved inwards, making the rectangle narrower. If dx is negative, then the
       * sides are moved outwards, making the rectangle wider. The same holds true
       * for dy and the top and bottom.
       *
       * @param dx The amount to add(subtract) from the rectangle's left(right)
       * @param dy The amount to add(subtract) from the rectangle's top(bottom)
       */
      public void inset(int dx, int dy) {
          left += dx;
          top += dy;
          right -= dx;
          bottom -= dy;
      }
  
      /**
       * Insets the rectangle on all sides specified by the dimensions of the {@code insets}
       * rectangle.
       * @hide
       * @param insets The rectangle specifying the insets on all side.
       */
      public void inset(@NonNull Rect insets) {
          left += insets.left;
          top += insets.top;
          right -= insets.right;
          bottom -= insets.bottom;
      }
  
      /**
       * Insets the rectangle on all sides specified by the dimensions of {@code insets}.
       * @hide
       * @param insets The insets to inset the rect by.
       */
      public void inset(Insets insets) {
          left += insets.left;
          top += insets.top;
          right -= insets.right;
          bottom -= insets.bottom;
      }
  
      /**
       * Insets the rectangle on all sides specified by the insets.
       * @hide
       * @param left The amount to add from the rectangle's left
       * @param top The amount to add from the rectangle's top
       * @param right The amount to subtract from the rectangle's right
       * @param bottom The amount to subtract from the rectangle's bottom
       */
      public void inset(int left, int top, int right, int bottom) {
          this.left += left;
          this.top += top;
          this.right -= right;
          this.bottom -= bottom;
      }
  
      /**
       * Returns true if (x,y) is inside the rectangle. The left and top are
       * considered to be inside, while the right and bottom are not. This means
       * that for a x,y to be contained: left <= x < right and top <= y < bottom.
       * An empty rectangle never contains any point.
       *
       * @param x The X coordinate of the point being tested for containment
       * @param y The Y coordinate of the point being tested for containment
       * @return true iff (x,y) are contained by the rectangle, where containment
       *              means left <= x < right and top <= y < bottom
       */
      public boolean contains(int x, int y) {
          return left < right && top < bottom  // check for empty first
                 && x >= left && x < right && y >= top && y < bottom;
      }
  
      /**
       * Returns true iff the 4 specified sides of a rectangle are inside or equal
       * to this rectangle. i.e. is this rectangle a superset of the specified
       * rectangle. An empty rectangle never contains another rectangle.
       *
       * @param left The left side of the rectangle being tested for containment
       * @param top The top of the rectangle being tested for containment
       * @param right The right side of the rectangle being tested for containment
       * @param bottom The bottom of the rectangle being tested for containment
       * @return true iff the the 4 specified sides of a rectangle are inside or
       *              equal to this rectangle
       */
      public boolean contains(int left, int top, int right, int bottom) {
                 // check for empty first
          return this.left < this.right && this.top < this.bottom
                 // now check for containment
                  && this.left <= left && this.top <= top
                  && this.right >= right && this.bottom >= bottom;
      }
  
      /**
       * Returns true iff the specified rectangle r is inside or equal to this
       * rectangle. An empty rectangle never contains another rectangle.
       *
       * @param r The rectangle being tested for containment.
       * @return true iff the specified rectangle r is inside or equal to this
       *              rectangle
       */
      public boolean contains(@NonNull Rect r) {
                 // check for empty first
          return this.left < this.right && this.top < this.bottom
                 // now check for containment
                 && left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom;
      }
  
      /**
       * If the rectangle specified by left,top,right,bottom intersects this
       * rectangle, return true and set this rectangle to that intersection,
       * otherwise return false and do not change this rectangle. No check is
       * performed to see if either rectangle is empty. Note: To just test for
       * intersection, use {@link #intersects(Rect, Rect)}.
       *
       * @param left The left side of the rectangle being intersected with this
       *             rectangle
       * @param top The top of the rectangle being intersected with this rectangle
       * @param right The right side of the rectangle being intersected with this
       *              rectangle.
       * @param bottom The bottom of the rectangle being intersected with this
       *             rectangle.
       * @return true if the specified rectangle and this rectangle intersect
       *              (and this rectangle is then set to that intersection) else
       *              return false and do not change this rectangle.
       */
      @CheckResult
      public boolean intersect(int left, int top, int right, int bottom) {
          if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) {
              if (this.left < left) this.left = left;
              if (this.top < top) this.top = top;
              if (this.right > right) this.right = right;
              if (this.bottom > bottom) this.bottom = bottom;
              return true;
          }
          return false;
      }
      
      /**
       * If the specified rectangle intersects this rectangle, return true and set
       * this rectangle to that intersection, otherwise return false and do not
       * change this rectangle. No check is performed to see if either rectangle
       * is empty. To just test for intersection, use intersects()
       *
       * @param r The rectangle being intersected with this rectangle.
       * @return true if the specified rectangle and this rectangle intersect
       *              (and this rectangle is then set to that intersection) else
       *              return false and do not change this rectangle.
       */
      @CheckResult
      public boolean intersect(@NonNull Rect r) {
          return intersect(r.left, r.top, r.right, r.bottom);
      }
  
      /**
       * If the specified rectangle intersects this rectangle, set this rectangle to that
       * intersection, otherwise set this rectangle to the empty rectangle.
       * @see #inset(int, int, int, int) but without checking if the rects overlap.
       * @hide
       */
      public void intersectUnchecked(@NonNull Rect other) {
          left = Math.max(left, other.left);
          top = Math.max(top, other.top);
          right = Math.min(right, other.right);
          bottom = Math.min(bottom, other.bottom);
      }
  
      /**
       * If rectangles a and b intersect, return true and set this rectangle to
       * that intersection, otherwise return false and do not change this
       * rectangle. No check is performed to see if either rectangle is empty.
       * To just test for intersection, use intersects()
       *
       * @param a The first rectangle being intersected with
       * @param b The second rectangle being intersected with
       * @return true iff the two specified rectangles intersect. If they do, set
       *              this rectangle to that intersection. If they do not, return
       *              false and do not change this rectangle.
       */
      @CheckResult
      public boolean setIntersect(@NonNull Rect a, @NonNull Rect b) {
          if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) {
              left = Math.max(a.left, b.left);
              top = Math.max(a.top, b.top);
              right = Math.min(a.right, b.right);
              bottom = Math.min(a.bottom, b.bottom);
              return true;
          }
          return false;
      }
  
      /**
       * Returns true if this rectangle intersects the specified rectangle.
       * In no event is this rectangle modified. No check is performed to see
       * if either rectangle is empty. To record the intersection, use intersect()
       * or setIntersect().
       *
       * @param left The left side of the rectangle being tested for intersection
       * @param top The top of the rectangle being tested for intersection
       * @param right The right side of the rectangle being tested for
       *              intersection
       * @param bottom The bottom of the rectangle being tested for intersection
       * @return true iff the specified rectangle intersects this rectangle. In
       *              no event is this rectangle modified.
       */
      public boolean intersects(int left, int top, int right, int bottom) {
          return this.left < right && left < this.right && this.top < bottom && top < this.bottom;
      }
  
      /**
       * Returns true iff the two specified rectangles intersect. In no event are
       * either of the rectangles modified. To record the intersection,
       * use {@link #intersect(Rect)} or {@link #setIntersect(Rect, Rect)}.
       *
       * @param a The first rectangle being tested for intersection
       * @param b The second rectangle being tested for intersection
       * @return true iff the two specified rectangles intersect. In no event are
       *              either of the rectangles modified.
       */
      public static boolean intersects(@NonNull Rect a, @NonNull Rect b) {
          return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
      }
  
      /**
       * Update this Rect to enclose itself and the specified rectangle. If the
       * specified rectangle is empty, nothing is done. If this rectangle is empty
       * it is set to the specified rectangle.
       *
       * @param left The left edge being unioned with this rectangle
       * @param top The top edge being unioned with this rectangle
       * @param right The right edge being unioned with this rectangle
       * @param bottom The bottom edge being unioned with this rectangle
       */
      public void union(int left, int top, int right, int bottom) {
          if ((left < right) && (top < bottom)) {
              if ((this.left < this.right) && (this.top < this.bottom)) {
                  if (this.left > left) this.left = left;
                  if (this.top > top) this.top = top;
                  if (this.right < right) this.right = right;
                  if (this.bottom < bottom) this.bottom = bottom;
              } else {
                  this.left = left;
                  this.top = top;
                  this.right = right;
                  this.bottom = bottom;
              }
          }
      }
  
      /**
       * Update this Rect to enclose itself and the specified rectangle. If the
       * specified rectangle is empty, nothing is done. If this rectangle is empty
       * it is set to the specified rectangle.
       *
       * @param r The rectangle being unioned with this rectangle
       */
      public void union(@NonNull Rect r) {
          union(r.left, r.top, r.right, r.bottom);
      }
      
      /**
       * Update this Rect to enclose itself and the [x,y] coordinate. There is no
       * check to see that this rectangle is non-empty.
       *
       * @param x The x coordinate of the point to add to the rectangle
       * @param y The y coordinate of the point to add to the rectangle
       */
      public void union(int x, int y) {
          if (x < left) {
              left = x;
          } else if (x > right) {
              right = x;
          }
          if (y < top) {
              top = y;
          } else if (y > bottom) {
              bottom = y;
          }
      }
  
      /**
       * Swap top/bottom or left/right if there are flipped (i.e. left > right
       * and/or top > bottom). This can be called if
       * the edges are computed separately, and may have crossed over each other.
       * If the edges are already correct (i.e. left <= right and top <= bottom)
       * then nothing is done.
       */
      public void sort() {
          if (left > right) {
              int temp = left;
              left = right;
              right = temp;
          }
          if (top > bottom) {
              int temp = top;
              top = bottom;
              bottom = temp;
          }
      }
  
      /**
       * Parcelable interface methods
       */
      @Override
      public int describeContents() {
          return 0;
      }
  
      /**
       * Write this rectangle to the specified parcel. To restore a rectangle from
       * a parcel, use readFromParcel()
       * @param out The parcel to write the rectangle's coordinates into
       */
      @Override
      public void writeToParcel(Parcel out, int flags) {
          out.writeInt(left);
          out.writeInt(top);
          out.writeInt(right);
          out.writeInt(bottom);
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
          /**
           * Return a new rectangle from the data in the specified parcel.
           */
          @Override
          public Rect createFromParcel(Parcel in) {
              Rect r = new Rect();
              r.readFromParcel(in);
              return r;
          }
  
          /**
           * Return an array of rectangles of the specified size.
           */
          @Override
          public Rect[] newArray(int size) {
              return new Rect[size];
          }
      };
  
      /**
       * Set the rectangle's coordinates from the data stored in the specified
       * parcel. To write a rectangle to a parcel, call writeToParcel().
       *
       * @param in The parcel to read the rectangle's coordinates from
       */
      public void readFromParcel(@NonNull Parcel in) {
          left = in.readInt();
          top = in.readInt();
          right = in.readInt();
          bottom = in.readInt();
      }
  
      /**
       * Scales up the rect by the given scale.
       * @hide
       */
      @UnsupportedAppUsage
      public void scale(float scale) {
          if (scale != 1.0f) {
              left = (int) (left * scale + 0.5f);
              top = (int) (top * scale + 0.5f);
              right = (int) (right * scale + 0.5f);
              bottom = (int) (bottom * scale + 0.5f);
          }
      }
  
  }
  