1 /* 2 * Copyright (C) 2018 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.graphics.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertTrue; 22 23 import android.content.res.AssetManager; 24 import android.graphics.Typeface; 25 import android.graphics.fonts.Font; 26 import android.graphics.fonts.FontFamily; 27 import android.graphics.fonts.FontStyle; 28 import android.graphics.fonts.FontTestUtil; 29 import android.text.TextPaint; 30 import android.util.Pair; 31 32 import androidx.test.InstrumentationRegistry; 33 import androidx.test.filters.SmallTest; 34 import androidx.test.runner.AndroidJUnit4; 35 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 39 import java.io.IOException; 40 import java.util.Locale; 41 42 @SmallTest 43 @RunWith(AndroidJUnit4.class) 44 public class TypefaceCustomFallbackBuilderTest { 45 46 /** 47 * Returns Typeface with a font family which has 100-900 weight and upright/italic style fonts. 48 */ 49 private Typeface createFullFamilyTypeface() throws IOException { 50 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 51 FontFamily.Builder b = null; 52 for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) { 53 final int weight = style.first.intValue(); 54 final boolean italic = style.second.booleanValue(); 55 56 if (b == null) { 57 b = new FontFamily.Builder(new Font.Builder(am, 58 FontTestUtil.getFontPathFromStyle(weight, italic)).build()); 59 } else { 60 b.addFont(new Font.Builder(am, 61 FontTestUtil.getFontPathFromStyle(weight, italic)).build()); 62 } 63 } 64 return new Typeface.CustomFallbackBuilder(b.build()).build(); 65 } 66 67 @Test 68 public void testSingleFont_path() throws IOException { 69 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 70 for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) { 71 final int weight = style.first.intValue(); 72 final boolean italic = style.second.booleanValue(); 73 74 final String path = FontTestUtil.getFontPathFromStyle(weight, italic); 75 assertEquals(path, FontTestUtil.getSelectedFontPathInAsset( 76 new Typeface.CustomFallbackBuilder( 77 new FontFamily.Builder( 78 new Font.Builder(am, path).build()).build()).build())); 79 } 80 } 81 82 @Test 83 public void testSingleFont_ttc() throws IOException { 84 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 85 for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) { 86 final int weight = style.first.intValue(); 87 final boolean italic = style.second.booleanValue(); 88 89 final int ttcIndex = FontTestUtil.getTtcIndexFromStyle(weight, italic); 90 assertEquals(ttcIndex, FontTestUtil.getSelectedTtcIndex( 91 new Typeface.CustomFallbackBuilder( 92 new FontFamily.Builder( 93 new Font.Builder(am, FontTestUtil.getTtcFontFileInAsset()) 94 .setTtcIndex(ttcIndex).build()).build()).build())); 95 } 96 } 97 98 @Test 99 public void testSingleFont_vf() throws IOException { 100 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 101 for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) { 102 final int weight = style.first.intValue(); 103 final boolean italic = style.second.booleanValue(); 104 105 final String varSettings = FontTestUtil.getVarSettingsFromStyle(weight, italic); 106 assertEquals(varSettings, FontTestUtil.getSelectedVariationSettings( 107 new Typeface.CustomFallbackBuilder(new FontFamily.Builder( 108 new Font.Builder(am, FontTestUtil.getVFFontInAsset()) 109 .setFontVariationSettings(varSettings).build()).build()).build())); 110 } 111 } 112 113 @Test 114 public void testFamily_defaultStyle() throws IOException { 115 final Typeface typeface = createFullFamilyTypeface(); 116 // If none of setWeight/setItalic is called, the default style(400, upright) is selected. 117 assertEquals(new Pair<Integer, Boolean>(400, false), 118 FontTestUtil.getSelectedStyle(typeface)); 119 } 120 121 @Test 122 public void testFamily_selectStyle() throws IOException { 123 final Typeface typeface = createFullFamilyTypeface(); 124 for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) { 125 final int weight = style.first.intValue(); 126 final boolean italic = style.second.booleanValue(); 127 assertEquals(style, 128 FontTestUtil.getSelectedStyle(Typeface.create(typeface, weight, italic))); 129 } 130 } 131 132 @Test 133 public void testFamily_selectStyleByBuilder() throws IOException { 134 for (Pair<Integer, Boolean> testStyle : FontTestUtil.getAllStyles()) { 135 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 136 FontFamily.Builder b = null; 137 for (Pair<Integer, Boolean> familyStyle : FontTestUtil.getAllStyles()) { 138 final int weight = familyStyle.first.intValue(); 139 final boolean italic = familyStyle.second.booleanValue(); 140 141 if (b == null) { 142 b = new FontFamily.Builder(new Font.Builder(am, 143 FontTestUtil.getFontPathFromStyle(weight, italic)).build()); 144 } else { 145 b.addFont(new Font.Builder(am, 146 FontTestUtil.getFontPathFromStyle(weight, italic)).build()); 147 } 148 } 149 final Typeface typeface = new Typeface.CustomFallbackBuilder(b.build()) 150 .setStyle(new FontStyle(testStyle.first.intValue(), 151 testStyle.second.booleanValue() 152 ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT)).build(); 153 154 assertEquals(testStyle, FontTestUtil.getSelectedStyle(typeface)); 155 } 156 } 157 158 @Test 159 public void testFamily_closestDefault() throws IOException { 160 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 161 final FontFamily.Builder b = new FontFamily.Builder( 162 new Font.Builder(am, FontTestUtil.getFontPathFromStyle(300, false)).build()) 163 .addFont(new Font.Builder(am, 164 FontTestUtil.getFontPathFromStyle(700, false)).build()); 165 166 final Typeface typeface = new Typeface.CustomFallbackBuilder(b.build()).build(); 167 // If font family doesn't have 400/upright style, the default style should be closest font 168 // instead. 169 assertEquals(new Pair<Integer, Boolean>(300, false), 170 FontTestUtil.getSelectedStyle(typeface)); 171 172 // If 600 is specified, 700 is selected since it is closer than 300. 173 assertEquals(new Pair<Integer, Boolean>(700, false), 174 FontTestUtil.getSelectedStyle(Typeface.create(typeface, 600, false))); 175 } 176 177 @Test 178 public void testUserFallback() throws IOException { 179 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 180 final FontFamily baseFamily = new FontFamily.Builder( 181 new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build()).build(); 182 final FontFamily fallbackFamily = new FontFamily.Builder( 183 new Font.Builder(am, "fonts/user_fallback/hebrew.ttf").build()).build(); 184 185 // baseFamily supports all ASCII alphabet characters but not supports Hebrew characters. 186 // FallbackFamily suppors all Hebrew alphabet characters and its width is 2em. 187 final Typeface typeface = new Typeface.CustomFallbackBuilder(baseFamily) 188 .addCustomFallback(fallbackFamily) 189 .build(); 190 191 TextPaint paint = new TextPaint(); 192 paint.setTextSize(10.0f); // Make 1em = 10px. 193 paint.setTypeface(typeface); 194 195 assertEquals(10.0f, paint.measureText("a", 0, 1), 0.0f); 196 assertEquals(20.0f, paint.measureText("\u05D0", 0, 1), 0.0f); // Hebrew letter 197 } 198 199 @Test 200 public void testMaxCustomFallback() throws IOException { 201 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 202 final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build(); 203 final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder( 204 new FontFamily.Builder(font).build()); 205 // Start from 1 since the first font family is already passed to the constructor. 206 for (int i = 1; i < Typeface.CustomFallbackBuilder.getMaxCustomFallbackCount(); ++i) { 207 b.addCustomFallback(new FontFamily.Builder(font).build()); 208 } 209 assertNotNull(b.build()); 210 } 211 212 @Test(expected = IllegalArgumentException.class) 213 public void testMaxCustomFallback_exceed_limits() throws IOException { 214 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 215 final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build(); 216 final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder( 217 new FontFamily.Builder(font).build()); 218 // Start from 1 since the first font family is already passed to the constructor. 219 for (int i = 1; i < Typeface.CustomFallbackBuilder.getMaxCustomFallbackCount() + 1; ++i) { 220 b.addCustomFallback(new FontFamily.Builder(font).build()); 221 } 222 } 223 224 @Test 225 public void testMaxCustomFallbackAtLeast64() throws IOException { 226 assertTrue(Typeface.CustomFallbackBuilder.getMaxCustomFallbackCount() >= 64); 227 } 228 229 @Test 230 public void testMaxCustomFallback_must_be_positive() { 231 assertTrue(Typeface.CustomFallbackBuilder.getMaxCustomFallbackCount() > 0); 232 } 233 234 @Test 235 public void testUserFallbackOverLocaleFallback() throws IOException { 236 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 237 final FontFamily baseFamily = new FontFamily.Builder( 238 new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build()).build(); 239 final FontFamily fallbackFamily = new FontFamily.Builder( 240 new Font.Builder(am, "fonts/user_fallback/ideograms.ttf").build()).build(); 241 242 // baseFamily supports all ASCII alphabet characters but not supports Hebrew characters. 243 // FallbackFamily suppors all Hebrew alphabet characters and its width is 2em. 244 final Typeface typeface = new Typeface.CustomFallbackBuilder(baseFamily) 245 .addCustomFallback(fallbackFamily) 246 .build(); 247 248 TextPaint paint = new TextPaint(); 249 paint.setTextSize(10.0f); // Make 1em = 10px. 250 paint.setTypeface(typeface); 251 paint.setTextLocale(Locale.JAPANESE); 252 253 assertEquals(10.0f, paint.measureText("a", 0, 1), 0.0f); 254 assertEquals(20.0f, paint.measureText("\u4E0D", 0, 1), 0.0f); // Hebrew letter 255 } 256 257 @Test 258 public void testSystemFallback_SansSerif() throws IOException { 259 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 260 final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build(); 261 { 262 final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder( 263 new FontFamily.Builder(font).build()); 264 assertNotNull(b.setSystemFallback("sans-serif").build()); 265 } 266 } 267 268 @Test 269 public void testSystemFallback_Serif() throws IOException { 270 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 271 final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build(); 272 { 273 final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder( 274 new FontFamily.Builder(font).build()); 275 assertNotNull(b.setSystemFallback("serif").build()); 276 } 277 } 278 279 @Test 280 public void testSystemFallback_anyString() throws IOException { 281 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 282 final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build(); 283 { 284 final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder( 285 new FontFamily.Builder(font).build()); 286 assertNotNull(b.setSystemFallback("any-string-is-fine").build()); 287 } 288 } 289 290 @Test 291 public void testSystemFallback_emptyString() throws IOException { 292 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 293 final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build(); 294 { 295 final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder( 296 new FontFamily.Builder(font).build()); 297 assertNotNull(b.setSystemFallback("").build()); 298 } 299 } 300 301 @Test(expected = NullPointerException.class) 302 public void testSystemFallback_null() throws IOException { 303 final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets(); 304 final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build(); 305 final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder( 306 new FontFamily.Builder(font).build()); 307 b.setSystemFallback(null); 308 } 309 } 310