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.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(LayoutLog.TAG_UNSUPPORTED, 146 "nativeCreateFromTypefaceWithVariation is not supported", 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(namespaces): 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 /*data*/); 270 } finally { 271 blockParser.ensurePopped(); 272 } 273 } else { 274 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 275 String.format("File %s does not exist (or is not a file)", path), 276 null /*data*/); 277 } 278 } else { 279 typeface = new Typeface.Builder(context.getAssets(), path).build(); 280 } 281 282 return typeface; 283 } 284 285 @LayoutlibDelegate create(String familyName, int style)286 /*package*/ static Typeface create(String familyName, int style) { 287 if (familyName != null && Files.exists(Paths.get(familyName))) { 288 // Workaround for b/64137851 289 // Support lib will call this method after failing to create the TypefaceCompat. 290 return Typeface_Delegate.createFromDisk(RenderAction.getCurrentContext(), familyName, 291 false); 292 } 293 return Typeface.create_Original(familyName, style); 294 } 295 296 @LayoutlibDelegate create(Typeface family, int style)297 /*package*/ static Typeface create(Typeface family, int style) { 298 return Typeface.create_Original(family, style); 299 } 300 301 @LayoutlibDelegate create(Typeface family, int style, boolean isItalic)302 /*package*/ static Typeface create(Typeface family, int style, boolean isItalic) { 303 return Typeface.create_Original(family, style, isItalic); 304 } 305 306 @LayoutlibDelegate nativeRegisterGenericFamily(String str, long nativePtr)307 /*package*/ static void nativeRegisterGenericFamily(String str, long nativePtr) { 308 Typeface_Delegate delegate = sManager.getDelegate(nativePtr); 309 if (delegate == null) { 310 return; 311 } 312 sGenericNativeFamilies.put(str, delegate.mFontFamilies); 313 } 314 315 // ---- Private delegate/helper methods ---- 316 317 /** 318 * Return an Iterable of fonts that match the style and variant. The list is ordered 319 * according to preference of fonts. 320 * <p> 321 * The Iterator may contain null when the font failed to load. If null is reached when trying to 322 * render with this list of fonts, then a warning should be logged letting the user know that 323 * some font failed to load. 324 * 325 * @param variant The variant preferred. Can only be {@link FontVariant#COMPACT} or {@link 326 * FontVariant#ELEGANT} 327 */ 328 @NonNull getFonts(final FontVariant variant)329 public Iterable<Font> getFonts(final FontVariant variant) { 330 assert variant != FontVariant.NONE; 331 332 return new FontsIterator(mFontFamilies, mFontFamilyBuilders, variant, mWeight, mStyle); 333 } 334 335 private static class FontsIterator implements Iterator<Font>, Iterable<Font> { 336 private final FontFamily_Delegate[] fontFamilies; 337 private final FontFamily_Builder_Delegate[] fontFamilyBuilders; 338 private final int weight; 339 private final boolean isItalic; 340 private final FontVariant variant; 341 342 private int index = 0; 343 FontsIterator(@onNull FontFamily_Delegate[] fontFamilies, @NonNull FontFamily_Builder_Delegate[] fontFamilyBuilders, @NonNull FontVariant variant, int weight, int style)344 private FontsIterator(@NonNull FontFamily_Delegate[] fontFamilies, 345 @NonNull FontFamily_Builder_Delegate[] fontFamilyBuilders, 346 @NonNull FontVariant variant, int weight, int style) { 347 // Calculate the required weight based on style and weight of this typeface. 348 int boldExtraWeight = 349 ((style & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA); 350 this.weight = Math.min(Math.max(100, weight + 50 + boldExtraWeight), 1000); 351 this.isItalic = (style & Font.ITALIC) != 0; 352 this.fontFamilies = fontFamilies; 353 this.fontFamilyBuilders = fontFamilyBuilders; 354 this.variant = variant; 355 } 356 357 @Override hasNext()358 public boolean hasNext() { 359 return index < (fontFamilies.length + fontFamilyBuilders.length); 360 } 361 362 @Override 363 @Nullable next()364 public Font next() { 365 Font font; 366 FontVariant ffdVariant; 367 if (index < fontFamilies.length) { 368 FontFamily_Delegate ffd = fontFamilies[index++]; 369 if (ffd == null || !ffd.isValid()) { 370 return null; 371 } 372 font = ffd.getFont(weight, isItalic); 373 ffdVariant = ffd.getVariant(); 374 } else { 375 FontFamily_Builder_Delegate ffd = fontFamilyBuilders[index++ - fontFamilies.length]; 376 if (ffd == null) { 377 return null; 378 } 379 font = ffd.getFont(weight, isItalic); 380 ffdVariant = ffd.getVariant(); 381 } 382 383 if (font == null) { 384 // The FontFamily is valid but doesn't contain any matching font. This means 385 // that the font failed to load. We add null to the list of fonts. Don't throw 386 // the warning just yet. If this is a non-english font, we don't want to warn 387 // users who are trying to render only english text. 388 return null; 389 } 390 391 if (ffdVariant == FontVariant.NONE || ffdVariant == variant) { 392 return font; 393 } 394 395 // We cannot open each font and get locales supported, etc to match the fonts. 396 // As a workaround, we hardcode certain assumptions like Elegant and Compact 397 // always appear in pairs. 398 if (index < fontFamilies.length) { 399 assert index < fontFamilies.length - 1; 400 FontFamily_Delegate ffd2 = fontFamilies[index++]; 401 assert ffd2 != null; 402 403 return ffd2.getFont(weight, isItalic); 404 } else { 405 assert index < fontFamilies.length + fontFamilyBuilders.length - 1; 406 FontFamily_Builder_Delegate ffd2 = fontFamilyBuilders[index++ - fontFamilies.length]; 407 assert ffd2 != null; 408 409 return ffd2.getFont(weight, isItalic); 410 } 411 } 412 413 @NonNull 414 @Override 415 public Iterator<Font> iterator() { 416 return this; 417 } 418 419 @Override 420 public Spliterator<Font> spliterator() { 421 return Spliterators.spliterator(iterator(), 422 fontFamilies.length + fontFamilyBuilders.length, 423 Spliterator.IMMUTABLE | Spliterator.SIZED); 424 } 425 } 426 } 427