• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.content.res;
18 
19 import android.annotation.NonNull;
20 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
21 import android.util.MathUtils;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 
25 import java.util.Arrays;
26 
27 /**
28  * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
29  * "dp" dimension according to a non-linear curve by interpolating values in a lookup table.
30  *
31  * {@see FontScaleConverter}
32  *
33  * @hide
34  */
35 // Needs to be public so the Kotlin test can see it
36 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
37 @RavenwoodKeepWholeClass
38 public class FontScaleConverterImpl implements FontScaleConverter {
39 
40     /** @hide */
41     @VisibleForTesting
42     public final float[] mFromSpValues;
43     /** @hide */
44     @VisibleForTesting
45     public final float[] mToDpValues;
46 
47     /**
48      * Creates a lookup table for the given conversions.
49      *
50      * <p>Any "sp" value not in the lookup table will be derived via linear interpolation.
51      *
52      * <p>The arrays must be sorted ascending and monotonically increasing.
53      *
54      * @param fromSp array of dimensions in SP
55      * @param toDp array of dimensions in DP that correspond to an SP value in fromSp
56      *
57      * @throws IllegalArgumentException if the array lengths don't match or are empty
58      * @hide
59      */
60     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
FontScaleConverterImpl(@onNull float[] fromSp, @NonNull float[] toDp)61     public FontScaleConverterImpl(@NonNull float[] fromSp, @NonNull float[] toDp) {
62         if (fromSp.length != toDp.length || fromSp.length == 0) {
63             throw new IllegalArgumentException("Array lengths must match and be nonzero");
64         }
65 
66         mFromSpValues = fromSp;
67         mToDpValues = toDp;
68     }
69 
70     /**
71      * Convert a dimension in "dp" back to "sp" using the lookup table.
72      *
73      * @hide
74      */
75     @Override
convertDpToSp(float dp)76     public float convertDpToSp(float dp) {
77         return lookupAndInterpolate(dp, mToDpValues, mFromSpValues);
78     }
79 
80     /**
81      * Convert a dimension in "sp" to "dp" using the lookup table.
82      *
83      * @hide
84      */
85     @Override
convertSpToDp(float sp)86     public float convertSpToDp(float sp) {
87         return lookupAndInterpolate(sp, mFromSpValues, mToDpValues);
88     }
89 
lookupAndInterpolate( float sourceValue, float[] sourceValues, float[] targetValues )90     private static float lookupAndInterpolate(
91             float sourceValue,
92             float[] sourceValues,
93             float[] targetValues
94     ) {
95         final float sourceValuePositive = Math.abs(sourceValue);
96         // TODO(b/247861374): find a match at a higher index?
97         final float sign = Math.signum(sourceValue);
98         // We search for exact matches only, even if it's just a little off. The interpolation will
99         // handle any non-exact matches.
100         final int index = Arrays.binarySearch(sourceValues, sourceValuePositive);
101         if (index >= 0) {
102             // exact match, return the matching dp
103             return sign * targetValues[index];
104         } else {
105             // must be a value in between index and index + 1: interpolate.
106             final int lowerIndex = -(index + 1) - 1;
107 
108             final float startSp;
109             final float endSp;
110             final float startDp;
111             final float endDp;
112 
113             if (lowerIndex >= sourceValues.length - 1) {
114                 // It's past our lookup table. Determine the last elements' scaling factor and use.
115                 startSp = sourceValues[sourceValues.length - 1];
116                 startDp = targetValues[sourceValues.length - 1];
117 
118                 if (startSp == 0) return 0;
119 
120                 final float scalingFactor = startDp / startSp;
121                 return sourceValue * scalingFactor;
122             } else if (lowerIndex == -1) {
123                 // It's smaller than the smallest value in our table. Interpolate from 0.
124                 startSp = 0;
125                 startDp = 0;
126                 endSp = sourceValues[0];
127                 endDp = targetValues[0];
128             } else {
129                 startSp = sourceValues[lowerIndex];
130                 endSp = sourceValues[lowerIndex + 1];
131                 startDp = targetValues[lowerIndex];
132                 endDp = targetValues[lowerIndex + 1];
133             }
134 
135             return sign
136                     * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, sourceValuePositive);
137         }
138     }
139 
140     @Override
equals(Object o)141     public boolean equals(Object o) {
142         if (this == o) return true;
143         if (o == null) return false;
144         if (!(o instanceof FontScaleConverterImpl)) return false;
145         FontScaleConverterImpl that = (FontScaleConverterImpl) o;
146         return Arrays.equals(mFromSpValues, that.mFromSpValues)
147                 && Arrays.equals(mToDpValues, that.mToDpValues);
148     }
149 
150     @Override
hashCode()151     public int hashCode() {
152         int result = Arrays.hashCode(mFromSpValues);
153         result = 31 * result + Arrays.hashCode(mToDpValues);
154         return result;
155     }
156 
157     @Override
toString()158     public String toString() {
159         return "FontScaleConverter{"
160                 + "fromSpValues="
161                 + Arrays.toString(mFromSpValues)
162                 + ", toDpValues="
163                 + Arrays.toString(mToDpValues)
164                 + '}';
165     }
166 }
167