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.annotation.Nullable; 21 import android.util.MathUtils; 22 import android.util.SparseArray; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 26 /** 27 * Stores lookup tables for creating {@link FontScaleConverter}s at various scales. 28 * 29 * @hide 30 */ 31 public class FontScaleConverterFactory { 32 private static final float SCALE_KEY_MULTIPLIER = 100f; 33 34 @VisibleForTesting 35 static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>(); 36 37 private static float sMinScaleBeforeCurvesApplied = 1.05f; 38 39 static { 40 // These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and 41 // manually tweaked for optimum readability. 42 put( 43 /* scaleKey= */ 1.15f, 44 new FontScaleConverter( 45 /* fromSp= */ 46 new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, 47 /* toDp= */ 48 new float[] { 9.2f, 11.5f, 13.8f, 16.4f, 19.8f, 21.8f, 25.2f, 30f, 100}) 49 ); 50 51 put( 52 /* scaleKey= */ 1.3f, 53 new FontScaleConverter( 54 /* fromSp= */ 55 new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, 56 /* toDp= */ 57 new float[] {10.4f, 13f, 15.6f, 18.8f, 21.6f, 23.6f, 26.4f, 30f, 100}) 58 ); 59 60 put( 61 /* scaleKey= */ 1.5f, 62 new FontScaleConverter( 63 /* fromSp= */ 64 new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, 65 /* toDp= */ 66 new float[] { 12f, 15f, 18f, 22f, 24f, 26f, 28f, 30f, 100}) 67 ); 68 69 put( 70 /* scaleKey= */ 1.8f, 71 new FontScaleConverter( 72 /* fromSp= */ 73 new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, 74 /* toDp= */ 75 new float[] {14.4f, 18f, 21.6f, 24.4f, 27.6f, 30.8f, 32.8f, 34.8f, 100}) 76 ); 77 78 put( 79 /* scaleKey= */ 2f, 80 new FontScaleConverter( 81 /* fromSp= */ 82 new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, 83 /* toDp= */ 84 new float[] { 16f, 20f, 24f, 26f, 30f, 34f, 36f, 38f, 100}) 85 ); 86 87 sMinScaleBeforeCurvesApplied = getScaleFromKey(LOOKUP_TABLES.keyAt(0)) - 0.02f; 88 if (sMinScaleBeforeCurvesApplied <= 1.0f) { 89 throw new IllegalStateException( 90 "You should only apply non-linear scaling to font scales > 1" 91 ); 92 } 93 } 94 FontScaleConverterFactory()95 private FontScaleConverterFactory() {} 96 97 /** 98 * Returns true if non-linear font scaling curves would be in effect for the given scale, false 99 * if the scaling would follow a linear curve or for no scaling. 100 * 101 * <p>Example usage: 102 * <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code> 103 * 104 * @hide 105 */ isNonLinearFontScalingActive(float fontScale)106 public static boolean isNonLinearFontScalingActive(float fontScale) { 107 return fontScale >= sMinScaleBeforeCurvesApplied; 108 } 109 110 /** 111 * Finds a matching FontScaleConverter for the given fontScale factor. 112 * 113 * @param fontScale the scale factor, usually from {@link Configuration#fontScale}. 114 * 115 * @return a converter for the given scale, or null if non-linear scaling should not be used. 116 * 117 * @hide 118 */ 119 @Nullable forScale(float fontScale)120 public static FontScaleConverter forScale(float fontScale) { 121 if (!isNonLinearFontScalingActive(fontScale)) { 122 return null; 123 } 124 125 FontScaleConverter lookupTable = get(fontScale); 126 if (lookupTable != null) { 127 return lookupTable; 128 } 129 130 // Didn't find an exact match: interpolate between two existing tables 131 final int index = LOOKUP_TABLES.indexOfKey(getKey(fontScale)); 132 if (index >= 0) { 133 // This should never happen, should have been covered by get() above. 134 return LOOKUP_TABLES.valueAt(index); 135 } 136 // Didn't find an exact match: interpolate between two existing tables 137 final int lowerIndex = -(index + 1) - 1; 138 final int higherIndex = lowerIndex + 1; 139 if (lowerIndex < 0 || higherIndex >= LOOKUP_TABLES.size()) { 140 // We have gone beyond our bounds and have nothing to interpolate between. Just give 141 // them a straight linear table instead. 142 // This works because when FontScaleConverter encounters a size beyond its bounds, it 143 // calculates a linear fontScale factor using the ratio of the last element pair. 144 return new FontScaleConverter(new float[] {1f}, new float[] {fontScale}); 145 } else { 146 float startScale = getScaleFromKey(LOOKUP_TABLES.keyAt(lowerIndex)); 147 float endScale = getScaleFromKey(LOOKUP_TABLES.keyAt(higherIndex)); 148 float interpolationPoint = MathUtils.constrainedMap( 149 /* rangeMin= */ 0f, 150 /* rangeMax= */ 1f, 151 startScale, 152 endScale, 153 fontScale 154 ); 155 return createInterpolatedTableBetween( 156 LOOKUP_TABLES.valueAt(lowerIndex), 157 LOOKUP_TABLES.valueAt(higherIndex), 158 interpolationPoint); 159 } 160 } 161 162 @NonNull createInterpolatedTableBetween( FontScaleConverter start, FontScaleConverter end, float interpolationPoint )163 private static FontScaleConverter createInterpolatedTableBetween( 164 FontScaleConverter start, 165 FontScaleConverter end, 166 float interpolationPoint 167 ) { 168 float[] commonSpSizes = new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100f}; 169 float[] dpInterpolated = new float[commonSpSizes.length]; 170 171 for (int i = 0; i < commonSpSizes.length; i++) { 172 float sp = commonSpSizes[i]; 173 float startDp = start.convertSpToDp(sp); 174 float endDp = end.convertSpToDp(sp); 175 dpInterpolated[i] = MathUtils.lerp(startDp, endDp, interpolationPoint); 176 } 177 178 return new FontScaleConverter(commonSpSizes, dpInterpolated); 179 } 180 getKey(float fontScale)181 private static int getKey(float fontScale) { 182 return (int) (fontScale * SCALE_KEY_MULTIPLIER); 183 } 184 getScaleFromKey(int key)185 private static float getScaleFromKey(int key) { 186 return (float) key / SCALE_KEY_MULTIPLIER; 187 } 188 put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter)189 private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) { 190 LOOKUP_TABLES.put(getKey(scaleKey), fontScaleConverter); 191 } 192 193 @Nullable get(float scaleKey)194 private static FontScaleConverter get(float scaleKey) { 195 return LOOKUP_TABLES.get(getKey(scaleKey)); 196 } 197 } 198