1 /* 2 * Copyright (C) 2010 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; 18 19 import com.android.SdkConstants; 20 import com.android.ide.common.rendering.api.LayoutLog; 21 import com.android.layoutlib.bridge.Bridge; 22 import com.android.layoutlib.bridge.android.BridgeContext; 23 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 24 import com.android.layoutlib.bridge.android.RenderParamsFlags; 25 import com.android.layoutlib.bridge.impl.DelegateManager; 26 import com.android.layoutlib.bridge.impl.ParserFactory; 27 import com.android.layoutlib.bridge.impl.RenderAction; 28 import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 29 30 import org.xmlpull.v1.XmlPullParser; 31 import org.xmlpull.v1.XmlPullParserException; 32 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.content.res.FontResourcesParser; 36 import android.graphics.FontFamily_Delegate.FontVariant; 37 import android.graphics.fonts.FontVariationAxis; 38 import android.text.FontConfig; 39 import android.util.ArrayMap; 40 41 import java.awt.Font; 42 import java.io.File; 43 import java.io.FileNotFoundException; 44 import java.io.IOException; 45 import java.lang.ref.SoftReference; 46 import java.nio.ByteBuffer; 47 import java.nio.file.Files; 48 import java.nio.file.Paths; 49 import java.util.ArrayList; 50 import java.util.EnumMap; 51 import java.util.Iterator; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Spliterator; 55 import java.util.Spliterators; 56 57 import static android.graphics.FontFamily_Delegate.getFontLocation; 58 59 /** 60 * Delegate implementing the native methods of android.graphics.Typeface 61 * <p> 62 * Through the layoutlib_create tool, the original native methods of Typeface have been replaced by 63 * calls to methods of the same name in this delegate class. 64 * <p> 65 * This class behaves like the original native implementation, but in Java, keeping previously 66 * native data into its own objects and mapping them to int that are sent back and forth between it 67 * and the original Typeface class. 68 * 69 * @see DelegateManager 70 */ 71 public final class Typeface_Delegate { 72 73 public static final String SYSTEM_FONTS = "/system/fonts/"; 74 75 // ---- delegate manager ---- 76 private static final DelegateManager<Typeface_Delegate> sManager = 77 new DelegateManager<>(Typeface_Delegate.class); 78 79 80 // ---- delegate data ---- 81 private static long sDefaultTypeface; 82 @NonNull 83 private final FontFamily_Delegate[] mFontFamilies; // the reference to FontFamily_Delegate. 84 /** @see Font#getStyle() */ 85 private final int mStyle; 86 private final int mWeight; 87 private SoftReference<EnumMap<FontVariant, List<Font>>> mFontsCache = new SoftReference<>(null); 88 89 90 // ---- Public Helper methods ---- 91 Typeface_Delegate(@onNull FontFamily_Delegate[] fontFamilies, int style, int weight)92 public Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style, int weight) { 93 mFontFamilies = fontFamilies; 94 mStyle = style; 95 mWeight = weight; 96 } 97 getDelegate(long nativeTypeface)98 public static Typeface_Delegate getDelegate(long nativeTypeface) { 99 return sManager.getDelegate(nativeTypeface); 100 } 101 102 /** 103 * Clear the default typefaces when disposing bridge. 104 */ resetDefaults()105 public static void resetDefaults() { 106 // Sometimes this is called before the Bridge is initialized. In that case, we don't want to 107 // initialize Typeface because the SDK fonts location hasn't been set. 108 if (FontFamily_Delegate.getFontLocation() != null) { 109 Typeface.sDefaults = null; 110 } 111 } 112 113 114 // ---- native methods ---- 115 116 @LayoutlibDelegate nativeCreateFromTypeface(long native_instance, int style)117 /*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) { 118 Typeface_Delegate delegate = sManager.getDelegate(native_instance); 119 if (delegate == null) { 120 delegate = sManager.getDelegate(sDefaultTypeface); 121 } 122 if (delegate == null) { 123 return 0; 124 } 125 126 return sManager.addNewDelegate( 127 new Typeface_Delegate(delegate.mFontFamilies, style, delegate.mWeight)); 128 } 129 130 @LayoutlibDelegate nativeCreateFromTypefaceWithExactStyle(long native_instance, int weight, boolean italic)131 /*package*/ static long nativeCreateFromTypefaceWithExactStyle(long native_instance, int weight, 132 boolean italic) { 133 Typeface_Delegate delegate = sManager.getDelegate(native_instance); 134 if (delegate == null) { 135 delegate = sManager.getDelegate(sDefaultTypeface); 136 } 137 if (delegate == null) { 138 return 0; 139 } 140 141 int style = weight >= 600 ? (italic ? Typeface.BOLD_ITALIC : Typeface.BOLD) : 142 (italic ? Typeface.ITALIC : Typeface.NORMAL); 143 return sManager.addNewDelegate( 144 new Typeface_Delegate(delegate.mFontFamilies, style, weight)); 145 } 146 147 @LayoutlibDelegate nativeCreateFromTypefaceWithVariation(long native_instance, List<FontVariationAxis> axes)148 /*package*/ static synchronized long nativeCreateFromTypefaceWithVariation(long native_instance, 149 List<FontVariationAxis> axes) { 150 long newInstance = nativeCreateFromTypeface(native_instance, 0); 151 152 if (newInstance != 0) { 153 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 154 "nativeCreateFromTypefaceWithVariation is not supported", null, null); 155 } 156 return newInstance; 157 } 158 159 @LayoutlibDelegate nativeGetSupportedAxes(long native_instance)160 /*package*/ static synchronized int[] nativeGetSupportedAxes(long native_instance) { 161 // nativeCreateFromTypefaceWithVariation is not supported so we do not keep the axes 162 return null; 163 } 164 165 @LayoutlibDelegate nativeCreateWeightAlias(long native_instance, int weight)166 /*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) { 167 Typeface_Delegate delegate = sManager.getDelegate(native_instance); 168 if (delegate == null) { 169 delegate = sManager.getDelegate(sDefaultTypeface); 170 } 171 if (delegate == null) { 172 return 0; 173 } 174 Typeface_Delegate weightAlias = 175 new Typeface_Delegate(delegate.mFontFamilies, delegate.mStyle, weight); 176 return sManager.addNewDelegate(weightAlias); 177 } 178 179 @LayoutlibDelegate nativeCreateFromArray(long[] familyArray, int weight, int italic)180 /*package*/ static synchronized long nativeCreateFromArray(long[] familyArray, int weight, 181 int italic) { 182 FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length]; 183 for (int i = 0; i < familyArray.length; i++) { 184 fontFamilies[i] = FontFamily_Delegate.getDelegate(familyArray[i]); 185 } 186 if (weight == Typeface.RESOLVE_BY_FONT_TABLE) { 187 weight = 400; 188 } 189 if (italic == Typeface.RESOLVE_BY_FONT_TABLE) { 190 italic = 0; 191 } 192 int style = weight >= 600 ? (italic == 1 ? Typeface.BOLD_ITALIC : Typeface.BOLD) : 193 (italic == 1 ? Typeface.ITALIC : Typeface.NORMAL); 194 Typeface_Delegate delegate = new Typeface_Delegate(fontFamilies, style, weight); 195 return sManager.addNewDelegate(delegate); 196 } 197 198 @LayoutlibDelegate nativeUnref(long native_instance)199 /*package*/ static void nativeUnref(long native_instance) { 200 sManager.removeJavaReferenceFor(native_instance); 201 } 202 203 @LayoutlibDelegate nativeGetStyle(long native_instance)204 /*package*/ static int nativeGetStyle(long native_instance) { 205 Typeface_Delegate delegate = sManager.getDelegate(native_instance); 206 if (delegate == null) { 207 return 0; 208 } 209 210 return delegate.mStyle; 211 } 212 213 @LayoutlibDelegate nativeSetDefault(long native_instance)214 /*package*/ static void nativeSetDefault(long native_instance) { 215 sDefaultTypeface = native_instance; 216 } 217 218 @LayoutlibDelegate nativeGetWeight(long native_instance)219 /*package*/ static int nativeGetWeight(long native_instance) { 220 Typeface_Delegate delegate = sManager.getDelegate(native_instance); 221 if (delegate == null) { 222 return 0; 223 } 224 return delegate.mWeight; 225 } 226 227 @LayoutlibDelegate buildSystemFallback(String xmlPath, String fontDir, ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap)228 /*package*/ static void buildSystemFallback(String xmlPath, String fontDir, 229 ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) { 230 Typeface.buildSystemFallback_Original(getFontLocation() + "/fonts.xml", fontDir, fontMap, 231 fallbackMap); 232 } 233 234 @LayoutlibDelegate createFontFamily(String familyName, List<FontConfig.Font> fonts, String[] languageTags, int variant, Map<String, ByteBuffer> cache, String fontDir)235 /*package*/ static FontFamily createFontFamily(String familyName, List<FontConfig.Font> fonts, 236 String[] languageTags, int variant, Map<String, ByteBuffer> cache, String fontDir) { 237 FontFamily fontFamily = new FontFamily(languageTags, variant); 238 for (FontConfig.Font font : fonts) { 239 String fullPathName = fontDir + font.getFontName(); 240 FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, fullPathName, font.getWeight(), 241 font.isItalic()); 242 } 243 fontFamily.freeze(); 244 return fontFamily; 245 } 246 247 /** 248 * Loads a single font or font family from disk 249 */ 250 @Nullable createFromDisk(@onNull BridgeContext context, @NonNull String path, boolean isFramework)251 public static Typeface createFromDisk(@NonNull BridgeContext context, @NonNull String path, 252 boolean isFramework) { 253 // Check if this is an asset that we've already loaded dynamically 254 Typeface typeface = Typeface.findFromCache(context.getAssets(), path); 255 if (typeface != null) { 256 return typeface; 257 } 258 259 String lowerCaseValue = path.toLowerCase(); 260 if (lowerCaseValue.endsWith(SdkConstants.DOT_XML)) { 261 // create a block parser for the file 262 Boolean psiParserSupport = context.getLayoutlibCallback().getFlag( 263 RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT); 264 XmlPullParser parser = null; 265 if (psiParserSupport != null && psiParserSupport) { 266 parser = context.getLayoutlibCallback().getXmlFileParser(path); 267 } else { 268 File f = new File(path); 269 if (f.isFile()) { 270 try { 271 parser = ParserFactory.create(f); 272 } catch (XmlPullParserException | FileNotFoundException e) { 273 // this is an error and not warning since the file existence is checked 274 // before 275 // attempting to parse it. 276 Bridge.getLog().error(null, "Failed to parse file " + path, e, 277 null /*data*/); 278 } 279 } 280 } 281 282 if (parser != null) { 283 BridgeXmlBlockParser blockParser = 284 new BridgeXmlBlockParser(parser, context, isFramework); 285 try { 286 FontResourcesParser.FamilyResourceEntry entry = 287 FontResourcesParser.parse(blockParser, context.getResources()); 288 typeface = Typeface.createFromResources(entry, context.getAssets(), path); 289 } catch (XmlPullParserException | IOException e) { 290 Bridge.getLog().error(null, "Failed to parse file " + path, e, null /*data*/); 291 } finally { 292 blockParser.ensurePopped(); 293 } 294 } else { 295 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 296 String.format("File %s does not exist (or is not a file)", path), 297 null /*data*/); 298 } 299 } else { 300 typeface = Typeface.createFromResources(context.getAssets(), path, 0); 301 } 302 303 return typeface; 304 } 305 306 @LayoutlibDelegate create(String familyName, int style)307 /*package*/ static Typeface create(String familyName, int style) { 308 if (familyName != null && Files.exists(Paths.get(familyName))) { 309 // Workaround for b/64137851 310 // Support lib will call this method after failing to create the TypefaceCompat. 311 return Typeface_Delegate.createFromDisk(RenderAction.getCurrentContext(), familyName, 312 false); 313 } 314 return Typeface.create_Original(familyName, style); 315 } 316 317 @LayoutlibDelegate create(Typeface family, int style)318 /*package*/ static Typeface create(Typeface family, int style) { 319 return Typeface.create_Original(family, style); 320 } 321 322 @LayoutlibDelegate create(Typeface family, int style, boolean isItalic)323 /*package*/ static Typeface create(Typeface family, int style, boolean isItalic) { 324 return Typeface.create_Original(family, style, isItalic); 325 } 326 327 // ---- Private delegate/helper methods ---- 328 computeFonts(FontVariant variant, FontFamily_Delegate[] fontFamilies, int inputWeight, int inputStyle)329 private static List<Font> computeFonts(FontVariant variant, FontFamily_Delegate[] fontFamilies, 330 int inputWeight, int inputStyle) { 331 // Calculate the required weight based on style and weight of this typeface. 332 int weight = inputWeight + 50 + 333 ((inputStyle & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA); 334 if (weight > 1000) { 335 weight = 1000; 336 } else if (weight < 100) { 337 weight = 100; 338 } 339 final boolean isItalic = (inputStyle & Font.ITALIC) != 0; 340 List<Font> fonts = new ArrayList<Font>(fontFamilies.length); 341 for (int i = 0; i < fontFamilies.length; i++) { 342 FontFamily_Delegate ffd = fontFamilies[i]; 343 if (ffd != null && ffd.isValid()) { 344 Font font = ffd.getFont(weight, isItalic); 345 if (font != null) { 346 FontVariant ffdVariant = ffd.getVariant(); 347 if (ffdVariant == FontVariant.NONE) { 348 fonts.add(font); 349 continue; 350 } 351 // We cannot open each font and get locales supported, etc to match the fonts. 352 // As a workaround, we hardcode certain assumptions like Elegant and Compact 353 // always appear in pairs. 354 assert i < fontFamilies.length - 1; 355 FontFamily_Delegate ffd2 = fontFamilies[++i]; 356 assert ffd2 != null; 357 FontVariant ffd2Variant = ffd2.getVariant(); 358 Font font2 = ffd2.getFont(weight, isItalic); 359 assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant && 360 font2 != null; 361 // Add the font with the matching variant to the list. 362 if (variant == ffd.getVariant()) { 363 fonts.add(font); 364 } else { 365 fonts.add(font2); 366 } 367 } else { 368 // The FontFamily is valid but doesn't contain any matching font. This means 369 // that the font failed to load. We add null to the list of fonts. Don't throw 370 // the warning just yet. If this is a non-english font, we don't want to warn 371 // users who are trying to render only english text. 372 fonts.add(null); 373 } 374 } 375 } 376 377 return fonts; 378 } 379 380 /** 381 * Return an Iterable of fonts that match the style and variant. The list is ordered 382 * according to preference of fonts. 383 * <p> 384 * The Iterator may contain null when the font failed to load. If null is reached when trying to 385 * render with this list of fonts, then a warning should be logged letting the user know that 386 * some font failed to load. 387 * 388 * @param variant The variant preferred. Can only be {@link FontVariant#COMPACT} or {@link 389 * FontVariant#ELEGANT} 390 */ 391 @NonNull 392 public Iterable<Font> getFonts(final FontVariant variant) { 393 assert variant != FontVariant.NONE; 394 395 return new FontsIterator(mFontFamilies, variant, mWeight, mStyle); 396 } 397 398 private static class FontsIterator implements Iterator<Font>, Iterable<Font> { 399 private final FontFamily_Delegate[] fontFamilies; 400 private final int weight; 401 private final boolean isItalic; 402 private final FontVariant variant; 403 404 private int index = 0; 405 406 private FontsIterator(@NonNull FontFamily_Delegate[] fontFamilies, 407 @NonNull FontVariant variant, int weight, int style) { 408 // Calculate the required weight based on style and weight of this typeface. 409 int boldExtraWeight = 410 ((style & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA); 411 this.weight = Math.min(Math.max(100, weight + 50 + boldExtraWeight), 1000); 412 this.isItalic = (style & Font.ITALIC) != 0; 413 this.fontFamilies = fontFamilies; 414 this.variant = variant; 415 } 416 417 @Override 418 public boolean hasNext() { 419 return index < fontFamilies.length; 420 } 421 422 @Override 423 @Nullable 424 public Font next() { 425 FontFamily_Delegate ffd = fontFamilies[index++]; 426 if (ffd == null || !ffd.isValid()) { 427 return null; 428 } 429 430 Font font = ffd.getFont(weight, isItalic); 431 if (font == null) { 432 // The FontFamily is valid but doesn't contain any matching font. This means 433 // that the font failed to load. We add null to the list of fonts. Don't throw 434 // the warning just yet. If this is a non-english font, we don't want to warn 435 // users who are trying to render only english text. 436 return null; 437 } 438 439 FontVariant ffdVariant = ffd.getVariant(); 440 if (ffdVariant == FontVariant.NONE) { 441 return font; 442 } 443 444 // We cannot open each font and get locales supported, etc to match the fonts. 445 // As a workaround, we hardcode certain assumptions like Elegant and Compact 446 // always appear in pairs. 447 assert index < fontFamilies.length - 1; 448 FontFamily_Delegate ffd2 = fontFamilies[index++]; 449 assert ffd2 != null; 450 451 if (ffdVariant == variant) { 452 return font; 453 } 454 455 FontVariant ffd2Variant = ffd2.getVariant(); 456 Font font2 = ffd2.getFont(weight, isItalic); 457 assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant && font2 != null; 458 // Add the font with the matching variant to the list. 459 return variant == ffd.getVariant() ? font : font2; 460 } 461 462 @NonNull 463 @Override 464 public Iterator<Font> iterator() { 465 return this; 466 } 467 468 @Override 469 public Spliterator<Font> spliterator() { 470 return Spliterators.spliterator(iterator(), fontFamilies.length, 471 Spliterator.IMMUTABLE | Spliterator.SIZED); 472 } 473 } 474 } 475