Loading core/java/android/content/res/CompatibilityInfo.java +3 −0 Original line number Diff line number Diff line Loading @@ -563,6 +563,9 @@ public class CompatibilityInfo implements Parcelable { if (applyToSize) { inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f); inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f); float fontScale = inoutDm.scaledDensity / inoutDm.density; inoutDm.fontScaleConverter = FontScaleConverterFactory.forScale(fontScale); } } Loading core/java/android/content/res/FontScaleConverter.java 0 → 100644 +148 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.content.res; import android.annotation.NonNull; import android.util.MathUtils; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; /** * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a * "dp" dimension according to a non-linear curve. * * <p>This is meant to improve readability at larger font scales: larger fonts will scale up more * slowly than smaller fonts, so we don't get ridiculously huge fonts that don't fit on the screen. * * <p>The thinking here is that large fonts are already big enough to read, but we still want to * scale them slightly to preserve the visual hierarchy when compared to smaller fonts. * * @hide */ public class FontScaleConverter { /** * How close the given SP should be to a canonical SP in the array before they are considered * the same for lookup purposes. */ private static final float THRESHOLD_FOR_MATCHING_SP = 0.02f; @VisibleForTesting final float[] mFromSpValues; @VisibleForTesting final float[] mToDpValues; /** * Creates a lookup table for the given conversions. * * <p>Any "sp" value not in the lookup table will be derived via linear interpolation. * * <p>The arrays must be sorted ascending and monotonically increasing. * * @param fromSp array of dimensions in SP * @param toDp array of dimensions in DP that correspond to an SP value in fromSp * * @throws IllegalArgumentException if the array lengths don't match or are empty * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public FontScaleConverter(@NonNull float[] fromSp, @NonNull float[] toDp) { if (fromSp.length != toDp.length || fromSp.length == 0) { throw new IllegalArgumentException("Array lengths must match and be nonzero"); } mFromSpValues = fromSp; mToDpValues = toDp; } /** * Convert a dimension in "sp" to "dp" using the lookup table. * * @hide */ public float convertSpToDp(float sp) { final float spPositive = Math.abs(sp); // TODO(b/247861374): find a match at a higher index? final int spRounded = Math.round(spPositive); final float sign = Math.signum(sp); final int index = Arrays.binarySearch(mFromSpValues, spRounded); if (index >= 0 && Math.abs(spRounded - spPositive) < THRESHOLD_FOR_MATCHING_SP) { // exact match, return the matching dp return sign * mToDpValues[index]; } else { // must be a value in between index and index + 1: interpolate. final int lowerIndex = -(index + 1) - 1; final float startSp; final float endSp; final float startDp; final float endDp; if (lowerIndex >= mFromSpValues.length - 1) { // It's past our lookup table. Determine the last elements' scaling factor and use. startSp = mFromSpValues[mFromSpValues.length - 1]; startDp = mToDpValues[mFromSpValues.length - 1]; if (startSp == 0) return 0; final float scalingFactor = startDp / startSp; return sp * scalingFactor; } else if (lowerIndex == -1) { // It's smaller than the smallest value in our table. Interpolate from 0. startSp = 0; startDp = 0; endSp = mFromSpValues[0]; endDp = mToDpValues[0]; } else { startSp = mFromSpValues[lowerIndex]; endSp = mFromSpValues[lowerIndex + 1]; startDp = mToDpValues[lowerIndex]; endDp = mToDpValues[lowerIndex + 1]; } return sign * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, spPositive); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (!(o instanceof FontScaleConverter)) return false; FontScaleConverter that = (FontScaleConverter) o; return Arrays.equals(mFromSpValues, that.mFromSpValues) && Arrays.equals(mToDpValues, that.mToDpValues); } @Override public int hashCode() { int result = Arrays.hashCode(mFromSpValues); result = 31 * result + Arrays.hashCode(mToDpValues); return result; } @Override public String toString() { return "FontScaleConverter{" + "fromSpValues=" + Arrays.toString(mFromSpValues) + ", toDpValues=" + Arrays.toString(mToDpValues) + '}'; } } core/java/android/content/res/FontScaleConverterFactory.java 0 → 100644 +119 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.content.res; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; /** * Stores lookup tables for creating {@link FontScaleConverter}s at various scales. * * @hide */ public class FontScaleConverterFactory { private static final float SCALE_KEY_MULTIPLIER = 100f; @VisibleForTesting static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>(); static { // These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and // manually tweaked for optimum readability. put( /* scaleKey= */ 1.15f, new FontScaleConverter( /* fromSp= */ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, /* toDp= */ new float[] { 9.2f, 11.5f, 13.8f, 16.1f, 20.7f, 23f, 27.6f, 34.5f, 115}) ); put( /* scaleKey= */ 1.3f, new FontScaleConverter( /* fromSp= */ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, /* toDp= */ new float[] {10.4f, 13f, 15.6f, 18.2f, 23.4f, 26f, 31.2f, 39f, 130}) ); put( /* scaleKey= */ 1.5f, new FontScaleConverter( /* fromSp= */ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, /* toDp= */ new float[] { 12f, 15f, 18f, 21f, 27f, 30f, 36f, 45f, 150}) ); put( /* scaleKey= */ 1.8f, new FontScaleConverter( /* fromSp= */ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, /* toDp= */ new float[] {14.4f, 18f, 21.6f, 25.2f, 32.4f, 36f, 43.2f, 54f, 180}) ); put( /* scaleKey= */ 2f, new FontScaleConverter( /* fromSp= */ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, /* toDp= */ new float[] { 16f, 20f, 24f, 28f, 36f, 40f, 48f, 60f, 200}) ); } private FontScaleConverterFactory() {} /** * Finds a matching FontScaleConverter for the given fontScale factor. * * @param fontScale the scale factor, usually from {@link Configuration#fontScale}. * * @return a converter for the given scale, or null if non-linear scaling should not be used. * * @hide */ @Nullable public static FontScaleConverter forScale(float fontScale) { if (fontScale <= 1) { // We don't need non-linear curves for shrinking text or for 100%. // Also, fontScale==0 should not have a curve either return null; } FontScaleConverter lookupTable = get(fontScale); // TODO(b/247861716): interpolate between two tables when null return lookupTable; } private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) { LOOKUP_TABLES.put((int) (scaleKey * SCALE_KEY_MULTIPLIER), fontScaleConverter); } @Nullable private static FontScaleConverter get(float scaleKey) { return LOOKUP_TABLES.get((int) (scaleKey * SCALE_KEY_MULTIPLIER)); } } core/java/android/content/res/ResourcesImpl.java +2 −0 Original line number Diff line number Diff line Loading @@ -434,6 +434,8 @@ public class ResourcesImpl { // Protect against an unset fontScale. mMetrics.scaledDensity = mMetrics.density * (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f); mMetrics.fontScaleConverter = FontScaleConverterFactory.forScale(mConfiguration.fontScale); final int width, height; if (mMetrics.widthPixels >= mMetrics.heightPixels) { Loading core/java/android/util/DisplayMetrics.java +12 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.util; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.FontScaleConverter; import android.os.SystemProperties; /** Loading Loading @@ -273,6 +274,15 @@ public class DisplayMetrics { * increments at runtime based on a user preference for the font size. */ public float scaledDensity; /** * If non-null, this will be used to calculate font sizes instead of {@link #scaledDensity}. * * @hide */ @Nullable public FontScaleConverter fontScaleConverter; /** * The exact physical pixels per inch of the screen in the X dimension. */ Loading Loading @@ -350,6 +360,7 @@ public class DisplayMetrics { noncompatScaledDensity = o.noncompatScaledDensity; noncompatXdpi = o.noncompatXdpi; noncompatYdpi = o.noncompatYdpi; fontScaleConverter = o.fontScaleConverter; } public void setToDefaults() { Loading @@ -367,6 +378,7 @@ public class DisplayMetrics { noncompatScaledDensity = scaledDensity; noncompatXdpi = xdpi; noncompatYdpi = ydpi; fontScaleConverter = null; } @Override Loading Loading
core/java/android/content/res/CompatibilityInfo.java +3 −0 Original line number Diff line number Diff line Loading @@ -563,6 +563,9 @@ public class CompatibilityInfo implements Parcelable { if (applyToSize) { inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f); inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f); float fontScale = inoutDm.scaledDensity / inoutDm.density; inoutDm.fontScaleConverter = FontScaleConverterFactory.forScale(fontScale); } } Loading
core/java/android/content/res/FontScaleConverter.java 0 → 100644 +148 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.content.res; import android.annotation.NonNull; import android.util.MathUtils; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; /** * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a * "dp" dimension according to a non-linear curve. * * <p>This is meant to improve readability at larger font scales: larger fonts will scale up more * slowly than smaller fonts, so we don't get ridiculously huge fonts that don't fit on the screen. * * <p>The thinking here is that large fonts are already big enough to read, but we still want to * scale them slightly to preserve the visual hierarchy when compared to smaller fonts. * * @hide */ public class FontScaleConverter { /** * How close the given SP should be to a canonical SP in the array before they are considered * the same for lookup purposes. */ private static final float THRESHOLD_FOR_MATCHING_SP = 0.02f; @VisibleForTesting final float[] mFromSpValues; @VisibleForTesting final float[] mToDpValues; /** * Creates a lookup table for the given conversions. * * <p>Any "sp" value not in the lookup table will be derived via linear interpolation. * * <p>The arrays must be sorted ascending and monotonically increasing. * * @param fromSp array of dimensions in SP * @param toDp array of dimensions in DP that correspond to an SP value in fromSp * * @throws IllegalArgumentException if the array lengths don't match or are empty * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public FontScaleConverter(@NonNull float[] fromSp, @NonNull float[] toDp) { if (fromSp.length != toDp.length || fromSp.length == 0) { throw new IllegalArgumentException("Array lengths must match and be nonzero"); } mFromSpValues = fromSp; mToDpValues = toDp; } /** * Convert a dimension in "sp" to "dp" using the lookup table. * * @hide */ public float convertSpToDp(float sp) { final float spPositive = Math.abs(sp); // TODO(b/247861374): find a match at a higher index? final int spRounded = Math.round(spPositive); final float sign = Math.signum(sp); final int index = Arrays.binarySearch(mFromSpValues, spRounded); if (index >= 0 && Math.abs(spRounded - spPositive) < THRESHOLD_FOR_MATCHING_SP) { // exact match, return the matching dp return sign * mToDpValues[index]; } else { // must be a value in between index and index + 1: interpolate. final int lowerIndex = -(index + 1) - 1; final float startSp; final float endSp; final float startDp; final float endDp; if (lowerIndex >= mFromSpValues.length - 1) { // It's past our lookup table. Determine the last elements' scaling factor and use. startSp = mFromSpValues[mFromSpValues.length - 1]; startDp = mToDpValues[mFromSpValues.length - 1]; if (startSp == 0) return 0; final float scalingFactor = startDp / startSp; return sp * scalingFactor; } else if (lowerIndex == -1) { // It's smaller than the smallest value in our table. Interpolate from 0. startSp = 0; startDp = 0; endSp = mFromSpValues[0]; endDp = mToDpValues[0]; } else { startSp = mFromSpValues[lowerIndex]; endSp = mFromSpValues[lowerIndex + 1]; startDp = mToDpValues[lowerIndex]; endDp = mToDpValues[lowerIndex + 1]; } return sign * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, spPositive); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (!(o instanceof FontScaleConverter)) return false; FontScaleConverter that = (FontScaleConverter) o; return Arrays.equals(mFromSpValues, that.mFromSpValues) && Arrays.equals(mToDpValues, that.mToDpValues); } @Override public int hashCode() { int result = Arrays.hashCode(mFromSpValues); result = 31 * result + Arrays.hashCode(mToDpValues); return result; } @Override public String toString() { return "FontScaleConverter{" + "fromSpValues=" + Arrays.toString(mFromSpValues) + ", toDpValues=" + Arrays.toString(mToDpValues) + '}'; } }
core/java/android/content/res/FontScaleConverterFactory.java 0 → 100644 +119 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.content.res; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; /** * Stores lookup tables for creating {@link FontScaleConverter}s at various scales. * * @hide */ public class FontScaleConverterFactory { private static final float SCALE_KEY_MULTIPLIER = 100f; @VisibleForTesting static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>(); static { // These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and // manually tweaked for optimum readability. put( /* scaleKey= */ 1.15f, new FontScaleConverter( /* fromSp= */ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, /* toDp= */ new float[] { 9.2f, 11.5f, 13.8f, 16.1f, 20.7f, 23f, 27.6f, 34.5f, 115}) ); put( /* scaleKey= */ 1.3f, new FontScaleConverter( /* fromSp= */ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, /* toDp= */ new float[] {10.4f, 13f, 15.6f, 18.2f, 23.4f, 26f, 31.2f, 39f, 130}) ); put( /* scaleKey= */ 1.5f, new FontScaleConverter( /* fromSp= */ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, /* toDp= */ new float[] { 12f, 15f, 18f, 21f, 27f, 30f, 36f, 45f, 150}) ); put( /* scaleKey= */ 1.8f, new FontScaleConverter( /* fromSp= */ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, /* toDp= */ new float[] {14.4f, 18f, 21.6f, 25.2f, 32.4f, 36f, 43.2f, 54f, 180}) ); put( /* scaleKey= */ 2f, new FontScaleConverter( /* fromSp= */ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, /* toDp= */ new float[] { 16f, 20f, 24f, 28f, 36f, 40f, 48f, 60f, 200}) ); } private FontScaleConverterFactory() {} /** * Finds a matching FontScaleConverter for the given fontScale factor. * * @param fontScale the scale factor, usually from {@link Configuration#fontScale}. * * @return a converter for the given scale, or null if non-linear scaling should not be used. * * @hide */ @Nullable public static FontScaleConverter forScale(float fontScale) { if (fontScale <= 1) { // We don't need non-linear curves for shrinking text or for 100%. // Also, fontScale==0 should not have a curve either return null; } FontScaleConverter lookupTable = get(fontScale); // TODO(b/247861716): interpolate between two tables when null return lookupTable; } private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) { LOOKUP_TABLES.put((int) (scaleKey * SCALE_KEY_MULTIPLIER), fontScaleConverter); } @Nullable private static FontScaleConverter get(float scaleKey) { return LOOKUP_TABLES.get((int) (scaleKey * SCALE_KEY_MULTIPLIER)); } }
core/java/android/content/res/ResourcesImpl.java +2 −0 Original line number Diff line number Diff line Loading @@ -434,6 +434,8 @@ public class ResourcesImpl { // Protect against an unset fontScale. mMetrics.scaledDensity = mMetrics.density * (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f); mMetrics.fontScaleConverter = FontScaleConverterFactory.forScale(mConfiguration.fontScale); final int width, height; if (mMetrics.widthPixels >= mMetrics.heightPixels) { Loading
core/java/android/util/DisplayMetrics.java +12 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.util; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.FontScaleConverter; import android.os.SystemProperties; /** Loading Loading @@ -273,6 +274,15 @@ public class DisplayMetrics { * increments at runtime based on a user preference for the font size. */ public float scaledDensity; /** * If non-null, this will be used to calculate font sizes instead of {@link #scaledDensity}. * * @hide */ @Nullable public FontScaleConverter fontScaleConverter; /** * The exact physical pixels per inch of the screen in the X dimension. */ Loading Loading @@ -350,6 +360,7 @@ public class DisplayMetrics { noncompatScaledDensity = o.noncompatScaledDensity; noncompatXdpi = o.noncompatXdpi; noncompatYdpi = o.noncompatYdpi; fontScaleConverter = o.fontScaleConverter; } public void setToDefaults() { Loading @@ -367,6 +378,7 @@ public class DisplayMetrics { noncompatScaledDensity = scaledDensity; noncompatXdpi = xdpi; noncompatYdpi = ydpi; fontScaleConverter = null; } @Override Loading