1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util;
18 
19 import static com.android.internal.util.Preconditions.checkArgumentFinite;
20 import static com.android.internal.util.Preconditions.checkNotNull;
21 
22 import android.annotation.NonNull;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 
26 /**
27  * Immutable class for describing width and height dimensions in some arbitrary
28  * unit.
29  * <p>
30  * Width and height are finite values stored as a floating point representation.
31  * </p>
32  */
33 public final class SizeF implements Parcelable {
34     /**
35      * Create a new immutable SizeF instance.
36      *
37      * <p>Both the {@code width} and the {@code height} must be a finite number.
38      * In particular, {@code NaN} and positive/negative infinity are illegal values.</p>
39      *
40      * @param width The width of the size
41      * @param height The height of the size
42      *
43      * @throws IllegalArgumentException
44      *             if either {@code width} or {@code height} was not finite.
45      */
SizeF(final float width, final float height)46     public SizeF(final float width, final float height) {
47         mWidth = checkArgumentFinite(width, "width");
48         mHeight = checkArgumentFinite(height, "height");
49     }
50 
51     /**
52      * Get the width of the size (as an arbitrary unit).
53      * @return width
54      */
getWidth()55     public float getWidth() {
56         return mWidth;
57     }
58 
59     /**
60      * Get the height of the size (as an arbitrary unit).
61      * @return height
62      */
getHeight()63     public float getHeight() {
64         return mHeight;
65     }
66 
67     /**
68      * Check if this size is equal to another size.
69      *
70      * <p>Two sizes are equal if and only if both their widths and heights are the same.</p>
71      *
72      * <p>For this purpose, the width/height float values are considered to be the same if and only
73      * if the method {@link Float#floatToIntBits(float)} returns the identical {@code int} value
74      * when applied to each.</p>
75      *
76      * @return {@code true} if the objects were equal, {@code false} otherwise
77      */
78     @Override
equals(final Object obj)79     public boolean equals(final Object obj) {
80         if (obj == null) {
81             return false;
82         }
83         if (this == obj) {
84             return true;
85         }
86         if (obj instanceof SizeF) {
87             final SizeF other = (SizeF) obj;
88             return mWidth == other.mWidth && mHeight == other.mHeight;
89         }
90         return false;
91     }
92 
93     /**
94      * Return the size represented as a string with the format {@code "WxH"}
95      *
96      * @return string representation of the size
97      */
98     @Override
toString()99     public String toString() {
100         return mWidth + "x" + mHeight;
101     }
102 
invalidSizeF(String s)103     private static NumberFormatException invalidSizeF(String s) {
104         throw new NumberFormatException("Invalid SizeF: \"" + s + "\"");
105     }
106 
107     /**
108      * Parses the specified string as a size value.
109      * <p>
110      * The ASCII characters {@code \}{@code u002a} ('*') and
111      * {@code \}{@code u0078} ('x') are recognized as separators between
112      * the width and height.</p>
113      * <p>
114      * For any {@code SizeF s}: {@code SizeF.parseSizeF(s.toString()).equals(s)}.
115      * However, the method also handles sizes expressed in the
116      * following forms:</p>
117      * <p>
118      * "<i>width</i>{@code x}<i>height</i>" or
119      * "<i>width</i>{@code *}<i>height</i>" {@code => new SizeF(width, height)},
120      * where <i>width</i> and <i>height</i> are string floats potentially
121      * containing a sign, such as "-10.3", "+7" or "5.2", but not containing
122      * an {@code 'x'} (such as a float in hexadecimal string format).</p>
123      *
124      * <pre>{@code
125      * SizeF.parseSizeF("3.2*+6").equals(new SizeF(3.2f, 6.0f)) == true
126      * SizeF.parseSizeF("-3x-6").equals(new SizeF(-3.0f, -6.0f)) == true
127      * SizeF.parseSizeF("4 by 3") => throws NumberFormatException
128      * }</pre>
129      *
130      * @param string the string representation of a size value.
131      * @return the size value represented by {@code string}.
132      *
133      * @throws NumberFormatException if {@code string} cannot be parsed
134      * as a size value.
135      * @throws NullPointerException if {@code string} was {@code null}
136      */
parseSizeF(String string)137     public static SizeF parseSizeF(String string)
138             throws NumberFormatException {
139         checkNotNull(string, "string must not be null");
140 
141         int sep_ix = string.indexOf('*');
142         if (sep_ix < 0) {
143             sep_ix = string.indexOf('x');
144         }
145         if (sep_ix < 0) {
146             throw invalidSizeF(string);
147         }
148         try {
149             return new SizeF(Float.parseFloat(string.substring(0, sep_ix)),
150                     Float.parseFloat(string.substring(sep_ix + 1)));
151         } catch (NumberFormatException e) {
152             throw invalidSizeF(string);
153         } catch (IllegalArgumentException e) {
154             throw invalidSizeF(string);
155         }
156     }
157 
158     /**
159      * {@inheritDoc}
160      */
161     @Override
hashCode()162     public int hashCode() {
163         return Float.floatToIntBits(mWidth) ^ Float.floatToIntBits(mHeight);
164     }
165 
166     private final float mWidth;
167     private final float mHeight;
168 
169     /**
170      * Parcelable interface methods
171      */
172     @Override
describeContents()173     public int describeContents() {
174         return 0;
175     }
176 
177     /**
178      * Write this size to the specified parcel. To restore a size from a parcel, use the
179      * {@link #CREATOR}.
180      * @param out The parcel to write the point's coordinates into
181      */
182     @Override
writeToParcel(@onNull Parcel out, int flags)183     public void writeToParcel(@NonNull Parcel out, int flags) {
184         out.writeFloat(mWidth);
185         out.writeFloat(mHeight);
186     }
187 
188     public static final @NonNull Creator<SizeF> CREATOR = new Creator<SizeF>() {
189         /**
190          * Return a new size from the data in the specified parcel.
191          */
192         @Override
193         public @NonNull SizeF createFromParcel(@NonNull Parcel in) {
194             float width = in.readFloat();
195             float height = in.readFloat();
196             return new SizeF(width, height);
197         }
198 
199         /**
200          * Return an array of sizes of the specified size.
201          */
202         @Override
203         public @NonNull SizeF[] newArray(int size) {
204             return new SizeF[size];
205         }
206     };
207 }
208