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