diff options
Diffstat (limited to 'graphics/java')
| -rw-r--r-- | graphics/java/android/graphics/Bitmap.java | 12 | ||||
| -rw-r--r-- | graphics/java/android/graphics/FontListParser.java | 3 | ||||
| -rw-r--r-- | graphics/java/android/graphics/LeakyTypefaceStorage.java | 86 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Paint.java | 33 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Typeface.java | 492 |
5 files changed, 412 insertions, 214 deletions
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index d586db438765..57c75490ec47 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -21,6 +21,7 @@ import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.content.res.ResourcesImpl; import android.os.Parcel; import android.os.Parcelable; import android.os.StrictMode; @@ -82,6 +83,12 @@ public final class Bitmap implements Parcelable { private static volatile int sDefaultDensity = -1; + /** @hide Used only when ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD is true. */ + public static volatile int sPreloadTracingNumInstantiatedBitmaps; + + /** @hide Used only when ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD is true. */ + public static volatile long sPreloadTracingTotalBitmapsSize; + /** * For backwards compatibility, allows the app layer to change the default * density when running old apps. @@ -128,6 +135,11 @@ public final class Bitmap implements Parcelable { NativeAllocationRegistry registry = new NativeAllocationRegistry( Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize); registry.registerNativeAllocation(this, nativeBitmap); + + if (ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD) { + sPreloadTracingNumInstantiatedBitmaps++; + sPreloadTracingTotalBitmapsSize += nativeSize; + } } /** diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 7c07a302dfe9..80a9324d04f3 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -111,6 +111,7 @@ public class FontListParser { String weightStr = parser.getAttributeValue(null, "weight"); int weight = weightStr == null ? 400 : Integer.parseInt(weightStr); boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style")); + String fallbackFor = parser.getAttributeValue(null, "fallbackFor"); StringBuilder filename = new StringBuilder(); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() == XmlPullParser.TEXT) { @@ -126,7 +127,7 @@ public class FontListParser { } String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll(""); return new FontConfig.Font(sanitizedName, index, - axes.toArray(new FontVariationAxis[axes.size()]), weight, isItalic); + axes.toArray(new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor); } private static FontVariationAxis readAxis(XmlPullParser parser) diff --git a/graphics/java/android/graphics/LeakyTypefaceStorage.java b/graphics/java/android/graphics/LeakyTypefaceStorage.java new file mode 100644 index 000000000000..618e60d442d7 --- /dev/null +++ b/graphics/java/android/graphics/LeakyTypefaceStorage.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 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.graphics; + +import com.android.internal.annotations.GuardedBy; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Process; +import android.util.ArrayMap; + +import java.util.ArrayList; + +/** + * This class is used for Parceling Typeface object. + * Note: Typeface object can not be passed over the process boundary. + * + * @hide + */ +public class LeakyTypefaceStorage { + private static final Object sLock = new Object(); + + @GuardedBy("sLock") + private static final ArrayList<Typeface> sStorage = new ArrayList<>(); + @GuardedBy("sLock") + private static final ArrayMap<Typeface, Integer> sTypefaceMap = new ArrayMap<>(); + + /** + * Write typeface to parcel. + * + * You can't transfer Typeface to a different process. {@link readTypefaceFromParcel} will + * return {@code null} if the {@link readTypefaceFromParcel} is called in a different process. + * + * @param typeface A {@link Typeface} to be written. + * @param parcel A {@link Parcel} object. + */ + public static void writeTypefaceToParcel(@Nullable Typeface typeface, @NonNull Parcel parcel) { + parcel.writeInt(Process.myPid()); + synchronized (sLock) { + final int id; + final Integer i = sTypefaceMap.get(typeface); + if (i != null) { + id = i.intValue(); + } else { + id = sStorage.size(); + sStorage.add(typeface); + sTypefaceMap.put(typeface, id); + } + parcel.writeInt(id); + } + } + + /** + * Read typeface from parcel. + * + * If the {@link Typeface} was created in another process, this method returns null. + * + * @param parcel A {@link Parcel} object + * @return A {@link Typeface} object. + */ + public static @Nullable Typeface readTypefaceFromParcel(@NonNull Parcel parcel) { + final int pid = parcel.readInt(); + final int typefaceId = parcel.readInt(); + if (pid != Process.myPid()) { + return null; // The Typeface was created and written in another process. + } + synchronized (sLock) { + return sStorage.get(typefaceId); + } + } +} diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index e1eb69a27e33..aa9227c9bb08 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -822,18 +822,14 @@ public class Paint { * @hide */ public float getUnderlinePosition() { - // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h - // TODO: replace with position from post and MVAR tables (b/62353930). - return (1.0f / 9.0f) * getTextSize(); + return nGetUnderlinePosition(mNativePaint, mNativeTypeface); } /** * @hide */ public float getUnderlineThickness() { - // kStdUnderline_Thickness = 1/18, defined in SkTextFormatParams.h - // TODO: replace with thickness from post and MVAR tables (b/62353930). - return (1.0f / 18.0f) * getTextSize(); + return nGetUnderlineThickness(mNativePaint, mNativeTypeface); } /** @@ -856,6 +852,23 @@ public class Paint { } /** + * Distance from top of the strike-through line to the baseline. Negative values mean above the + * baseline. This method returns where the strike-through line should be drawn independent of if + * the strikeThruText bit is set at the moment. + * @hide + */ + public float getStrikeThruPosition() { + return nGetStrikeThruPosition(mNativePaint, mNativeTypeface); + } + + /** + * @hide + */ + public float getStrikeThruThickness() { + return nGetStrikeThruThickness(mNativePaint, mNativeTypeface); + } + + /** * Helper for setFlags(), setting or clearing the STRIKE_THRU_TEXT_FLAG bit * * @param strikeThruText true to set the strikeThruText bit in the paint's @@ -2997,5 +3010,13 @@ public class Paint { @CriticalNative private static native float nDescent(long paintPtr, long typefacePtr); @CriticalNative + private static native float nGetUnderlinePosition(long paintPtr, long typefacePtr); + @CriticalNative + private static native float nGetUnderlineThickness(long paintPtr, long typefacePtr); + @CriticalNative + private static native float nGetStrikeThruPosition(long paintPtr, long typefacePtr); + @CriticalNative + private static native float nGetStrikeThruThickness(long paintPtr, long typefacePtr); + @CriticalNative private static native void nSetTextSize(long paintPtr, float textSize); } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index c4b56c333c64..1d8b5830aa92 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -38,6 +38,7 @@ import android.os.ResultReceiver; import android.provider.FontRequest; import android.provider.FontsContract; import android.text.FontConfig; +import android.util.ArrayMap; import android.util.Base64; import android.util.Log; import android.util.LongSparseArray; @@ -45,6 +46,7 @@ import android.util.LruCache; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import libcore.io.IoUtils; @@ -105,12 +107,10 @@ public class Typeface { private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16); static Typeface sDefaultTypeface; - static Map<String, Typeface> sSystemFontMap; - static FontFamily[] sFallbackFonts; + static final Map<String, Typeface> sSystemFontMap; + static final Map<String, FontFamily[]> sSystemFallbackMap; private static final Object sLock = new Object(); - static final String FONTS_CONFIG = "fonts.xml"; - /** * @hide */ @@ -129,6 +129,7 @@ public class Typeface { // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp /** @hide */ public static final int RESOLVE_BY_FONT_TABLE = -1; + private static final String DEFAULT_FAMILY = "sans-serif"; // Style value for building typeface. private static final int STYLE_NORMAL = 0; @@ -163,28 +164,27 @@ public class Typeface { */ @Nullable public static Typeface createFromResources(AssetManager mgr, String path, int cookie) { - if (sFallbackFonts != null) { - synchronized (sDynamicTypefaceCache) { - final String key = Builder.createAssetUid( - mgr, path, 0 /* ttcIndex */, null /* axes */, - RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */); - Typeface typeface = sDynamicTypefaceCache.get(key); - if (typeface != null) return typeface; - - FontFamily fontFamily = new FontFamily(); - // TODO: introduce ttc index and variation settings to resource type font. - if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */, - 0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */, - RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) { - if (!fontFamily.freeze()) { - return null; - } - FontFamily[] families = {fontFamily}; - typeface = createFromFamiliesWithDefault(families, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); - sDynamicTypefaceCache.put(key, typeface); - return typeface; + synchronized (sDynamicTypefaceCache) { + final String key = Builder.createAssetUid( + mgr, path, 0 /* ttcIndex */, null /* axes */, + RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, + DEFAULT_FAMILY); + Typeface typeface = sDynamicTypefaceCache.get(key); + if (typeface != null) return typeface; + + FontFamily fontFamily = new FontFamily(); + // TODO: introduce ttc index and variation settings to resource type font. + if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */, + 0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */, + RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) { + if (!fontFamily.freeze()) { + return null; } + FontFamily[] families = {fontFamily}; + typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY, + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); + sDynamicTypefaceCache.put(key, typeface); + return typeface; } } return null; @@ -197,61 +197,57 @@ public class Typeface { @Nullable public static Typeface createFromResources( FamilyResourceEntry entry, AssetManager mgr, String path) { - if (sFallbackFonts != null) { - if (entry instanceof ProviderResourceEntry) { - final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry; - // Downloadable font - List<List<String>> givenCerts = providerEntry.getCerts(); - List<List<byte[]>> certs = new ArrayList<>(); - if (givenCerts != null) { - for (int i = 0; i < givenCerts.size(); i++) { - List<String> certSet = givenCerts.get(i); - List<byte[]> byteArraySet = new ArrayList<>(); - for (int j = 0; j < certSet.size(); j++) { - byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT)); - } - certs.add(byteArraySet); + if (entry instanceof ProviderResourceEntry) { + final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry; + // Downloadable font + List<List<String>> givenCerts = providerEntry.getCerts(); + List<List<byte[]>> certs = new ArrayList<>(); + if (givenCerts != null) { + for (int i = 0; i < givenCerts.size(); i++) { + List<String> certSet = givenCerts.get(i); + List<byte[]> byteArraySet = new ArrayList<>(); + for (int j = 0; j < certSet.size(); j++) { + byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT)); } + certs.add(byteArraySet); } - // Downloaded font and it wasn't cached, request it again and return a - // default font instead (nothing we can do now). - FontRequest request = new FontRequest(providerEntry.getAuthority(), - providerEntry.getPackage(), providerEntry.getQuery(), certs); - Typeface typeface = FontsContract.getFontSync(request); - return typeface == null ? DEFAULT : typeface; } + // Downloaded font and it wasn't cached, request it again and return a + // default font instead (nothing we can do now). + FontRequest request = new FontRequest(providerEntry.getAuthority(), + providerEntry.getPackage(), providerEntry.getQuery(), certs); + Typeface typeface = FontsContract.getFontSync(request); + return typeface == null ? DEFAULT : typeface; + } - Typeface typeface = findFromCache(mgr, path); - if (typeface != null) return typeface; + Typeface typeface = findFromCache(mgr, path); + if (typeface != null) return typeface; - // family is FontFamilyFilesResourceEntry - final FontFamilyFilesResourceEntry filesEntry = - (FontFamilyFilesResourceEntry) entry; + // family is FontFamilyFilesResourceEntry + final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry; - FontFamily fontFamily = new FontFamily(); - for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { - // TODO: Add ttc and variation font support. (b/37853920) - if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(), - 0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */, - fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) { - return null; - } - } - if (!fontFamily.freeze()) { + FontFamily fontFamily = new FontFamily(); + for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { + // TODO: Add ttc and variation font support. (b/37853920) + if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(), + 0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */, + fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) { return null; } - FontFamily[] familyChain = { fontFamily }; - typeface = createFromFamiliesWithDefault(familyChain, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); - synchronized (sDynamicTypefaceCache) { - final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, - null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, - RESOLVE_BY_FONT_TABLE /* italic */); - sDynamicTypefaceCache.put(key, typeface); - } - return typeface; } - return null; + if (!fontFamily.freeze()) { + return null; + } + FontFamily[] familyChain = { fontFamily }; + typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY, + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); + synchronized (sDynamicTypefaceCache) { + final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, + null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, + RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY); + sDynamicTypefaceCache.put(key, typeface); + } + return typeface; } /** @@ -261,7 +257,8 @@ public class Typeface { public static Typeface findFromCache(AssetManager mgr, String path) { synchronized (sDynamicTypefaceCache) { final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */, - RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */); + RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, + DEFAULT_FAMILY); Typeface typeface = sDynamicTypefaceCache.get(key); if (typeface != null) { return typeface; @@ -498,7 +495,7 @@ public class Typeface { * @return Unique id for a given AssetManager and asset path. */ private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex, - @Nullable FontVariationAxis[] axes, int weight, int italic) { + @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) { final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers(); final StringBuilder builder = new StringBuilder(); final int size = pkgs.size(); @@ -513,7 +510,11 @@ public class Typeface { builder.append(Integer.toString(weight)); builder.append("-"); builder.append(Integer.toString(italic)); - builder.append("-"); + // Family name may contain hyphen. Use double hyphen for avoiding key conflicts before + // and after appending falblack name. + builder.append("--"); + builder.append(fallback); + builder.append("--"); if (axes != null) { for (FontVariationAxis axis : axes) { builder.append(axis.getTag()); @@ -593,13 +594,15 @@ public class Typeface { return resolveFallbackTypeface(); } FontFamily[] families = { fontFamily }; - return createFromFamiliesWithDefault(families, mWeight, mItalic); + return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight, + mItalic); } catch (IOException e) { return resolveFallbackTypeface(); } } else if (mAssetManager != null) { // Builder is created with asset manager. final String key = createAssetUid( - mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic); + mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic, + mFallbackFamilyName); synchronized (sLock) { Typeface typeface = sDynamicTypefaceCache.get(key); if (typeface != null) return typeface; @@ -613,7 +616,8 @@ public class Typeface { return resolveFallbackTypeface(); } FontFamily[] families = { fontFamily }; - typeface = createFromFamiliesWithDefault(families, mWeight, mItalic); + typeface = createFromFamiliesWithDefault(families, mFallbackFamilyName, + mWeight, mItalic); sDynamicTypefaceCache.put(key, typeface); return typeface; } @@ -627,7 +631,8 @@ public class Typeface { return resolveFallbackTypeface(); } FontFamily[] families = { fontFamily }; - return createFromFamiliesWithDefault(families, mWeight, mItalic); + return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight, + mItalic); } else if (mFonts != null) { final FontFamily fontFamily = new FontFamily(); boolean atLeastOneFont = false; @@ -653,7 +658,8 @@ public class Typeface { } fontFamily.freeze(); FontFamily[] families = { fontFamily }; - return createFromFamiliesWithDefault(families, mWeight, mItalic); + return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight, + mItalic); } // Must not reach here. @@ -673,10 +679,7 @@ public class Typeface { * @return The best matching typeface. */ public static Typeface create(String familyName, int style) { - if (sSystemFontMap != null) { - return create(sSystemFontMap.get(familyName), style); - } - return null; + return create(sSystemFontMap.get(familyName), style); } /** @@ -751,34 +754,33 @@ public class Typeface { if (path == null) { throw new NullPointerException(); // for backward compatibility } - if (sFallbackFonts != null) { - synchronized (sLock) { - Typeface typeface = new Builder(mgr, path).build(); - if (typeface != null) return typeface; + synchronized (sLock) { + Typeface typeface = new Builder(mgr, path).build(); + if (typeface != null) return typeface; - final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, - null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); - typeface = sDynamicTypefaceCache.get(key); - if (typeface != null) return typeface; + final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, + null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, + DEFAULT_FAMILY); + typeface = sDynamicTypefaceCache.get(key); + if (typeface != null) return typeface; - final FontFamily fontFamily = new FontFamily(); - if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */, - 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, - null /* axes */)) { - // Due to backward compatibility, even if the font is not supported by our font - // stack, we need to place the empty font at the first place. The typeface with - // empty font behaves different from default typeface especially in fallback - // font selection. - fontFamily.allowUnsupportedFont(); - fontFamily.freeze(); - final FontFamily[] families = { fontFamily }; - typeface = createFromFamiliesWithDefault(families, RESOLVE_BY_FONT_TABLE, - RESOLVE_BY_FONT_TABLE); - sDynamicTypefaceCache.put(key, typeface); - return typeface; - } else { - fontFamily.abortCreation(); - } + final FontFamily fontFamily = new FontFamily(); + if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */, + 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, + null /* axes */)) { + // Due to backward compatibility, even if the font is not supported by our font + // stack, we need to place the empty font at the first place. The typeface with + // empty font behaves different from default typeface especially in fallback + // font selection. + fontFamily.allowUnsupportedFont(); + fontFamily.freeze(); + final FontFamily[] families = { fontFamily }; + typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY, + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); + sDynamicTypefaceCache.put(key, typeface); + return typeface; + } else { + fontFamily.abortCreation(); } } throw new RuntimeException("Font asset not found " + path); @@ -815,22 +817,20 @@ public class Typeface { * @return The new typeface. */ public static Typeface createFromFile(@Nullable String path) { - if (sFallbackFonts != null) { - final FontFamily fontFamily = new FontFamily(); - if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) { - // Due to backward compatibility, even if the font is not supported by our font - // stack, we need to place the empty font at the first place. The typeface with - // empty font behaves different from default typeface especially in fallback font - // selection. - fontFamily.allowUnsupportedFont(); - fontFamily.freeze(); - FontFamily[] families = { fontFamily }; - return createFromFamiliesWithDefault(families, RESOLVE_BY_FONT_TABLE, - RESOLVE_BY_FONT_TABLE); - } else { - fontFamily.abortCreation(); - } + final FontFamily fontFamily = new FontFamily(); + if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */, + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) { + // Due to backward compatibility, even if the font is not supported by our font + // stack, we need to place the empty font at the first place. The typeface with + // empty font behaves different from default typeface especially in fallback font + // selection. + fontFamily.allowUnsupportedFont(); + fontFamily.freeze(); + FontFamily[] families = { fontFamily }; + return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); + } else { + fontFamily.abortCreation(); } throw new RuntimeException("Font not found " + path); } @@ -852,6 +852,8 @@ public class Typeface { /** * Create a new typeface from an array of font families, including * also the font families in the fallback list. + * @param fallbackName the family name. If given families don't support characters, the + * characters will be rendered with this family. * @param weight the weight for this family. {@link RESOLVE_BY_FONT_TABLE} can be used. In that * case, the table information in the first family's font is used. If the first * family has multiple fonts, the closest to the regular weight and upright font @@ -863,13 +865,17 @@ public class Typeface { * @param families array of font families */ private static Typeface createFromFamiliesWithDefault(FontFamily[] families, - int weight, int italic) { - long[] ptrArray = new long[families.length + sFallbackFonts.length]; + String fallbackName, int weight, int italic) { + FontFamily[] fallback = sSystemFallbackMap.get(fallbackName); + if (fallback == null) { + fallback = sSystemFallbackMap.get(DEFAULT_FAMILY); + } + long[] ptrArray = new long[families.length + fallback.length]; for (int i = 0; i < families.length; i++) { ptrArray[i] = families[i].mNativePtr; } - for (int i = 0; i < sFallbackFonts.length; i++) { - ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr; + for (int i = 0; i < fallback.length; i++) { + ptrArray[i + families.length] = fallback[i].mNativePtr; } return new Typeface(nativeCreateFromArray(ptrArray, weight, italic)); } @@ -885,113 +891,189 @@ public class Typeface { mWeight = nativeGetWeight(ni); } - private static FontFamily makeFamilyFromParsed(FontConfig.Family family, - Map<String, ByteBuffer> bufferForPath) { - FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant()); - for (FontConfig.Font font : family.getFonts()) { - String fullPathName = "/system/fonts/" + font.getFontName(); - ByteBuffer fontBuffer = bufferForPath.get(fullPathName); - if (fontBuffer == null) { - try (FileInputStream file = new FileInputStream(fullPathName)) { - FileChannel fileChannel = file.getChannel(); - long fontSize = fileChannel.size(); - fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); - bufferForPath.put(fullPathName, fontBuffer); - } catch (IOException e) { - Log.e(TAG, "Error mapping font file " + fullPathName); + private static @Nullable ByteBuffer mmap(String fullPath) { + try (FileInputStream file = new FileInputStream(fullPath)) { + final FileChannel fileChannel = file.getChannel(); + final long fontSize = fileChannel.size(); + return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); + } catch (IOException e) { + Log.e(TAG, "Error mapping font file " + fullPath); + return null; + } + } + + private static @Nullable FontFamily createFontFamily( + String familyName, List<FontConfig.Font> fonts, String languageTag, int variant, + Map<String, ByteBuffer> cache, String fontDir) { + final FontFamily family = new FontFamily(languageTag, variant); + for (int i = 0; i < fonts.size(); i++) { + final FontConfig.Font font = fonts.get(i); + final String fullPath = fontDir + font.getFontName(); + ByteBuffer buffer = cache.get(fullPath); + if (buffer == null) { + if (cache.containsKey(fullPath)) { + continue; // Already failed to mmap. Skip it. + } + buffer = mmap(fullPath); + cache.put(fullPath, buffer); + if (buffer == null) { continue; } } - if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(), + if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(), font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) { - Log.e(TAG, "Error creating font " + fullPathName + "#" + font.getTtcIndex()); + Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex()); } } - if (!fontFamily.freeze()) { - // Treat as system error since reaching here means that a system pre-installed font - // can't be used by our font stack. - Log.e(TAG, "Unable to load Family: " + family.getName() + ":" + family.getLanguage()); + if (!family.freeze()) { + Log.e(TAG, "Unable to load Family: " + familyName + " : " + languageTag); return null; } - return fontFamily; + return family; } - /* - * (non-Javadoc) + private static void pushFamilyToFallback(FontConfig.Family xmlFamily, + ArrayMap<String, ArrayList<FontFamily>> fallbackMap, + Map<String, ByteBuffer> cache, + String fontDir) { + + final String languageTag = xmlFamily.getLanguage(); + final int variant = xmlFamily.getVariant(); + + final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>(); + final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>(); + + // Collect default fallback and specific fallback fonts. + for (final FontConfig.Font font : xmlFamily.getFonts()) { + final String fallbackName = font.getFallbackFor(); + if (fallbackName == null) { + defaultFonts.add(font); + } else { + ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName); + if (fallback == null) { + fallback = new ArrayList<>(); + specificFallbackFonts.put(fallbackName, fallback); + } + fallback.add(font); + } + } + + final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( + xmlFamily.getName(), defaultFonts, languageTag, variant, cache, fontDir); + + // Insert family into fallback map. + for (int i = 0; i < fallbackMap.size(); i++) { + final ArrayList<FontConfig.Font> fallback = + specificFallbackFonts.get(fallbackMap.keyAt(i)); + if (fallback == null) { + if (defaultFamily != null) { + fallbackMap.valueAt(i).add(defaultFamily); + } + } else { + final FontFamily family = createFontFamily( + xmlFamily.getName(), fallback, languageTag, variant, cache, fontDir); + if (family != null) { + fallbackMap.valueAt(i).add(family); + } + } + } + } + + /** + * Build the system fallback from xml file. * - * This should only be called once, from the static class initializer block. + * @param xmlPath A full path string to the fonts.xml file. + * @param fontDir A full path string to the system font directory. This must end with + * slash('/'). + * @param fontMap An output system font map. Caller must pass empty map. + * @param fallbackMap An output system fallback map. Caller must pass empty map. + * @hide */ - private static void init() { - // Load font config and initialize Minikin state - File systemFontConfigLocation = getSystemFontConfigLocation(); - File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG); + @VisibleForTesting + public static void buildSystemFallback(String xmlPath, String fontDir, + ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) { try { - FileInputStream fontsIn = new FileInputStream(configFilename); - FontConfig fontConfig = FontListParser.parse(fontsIn); - - Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>(); - - List<FontFamily> familyList = new ArrayList<FontFamily>(); - // Note that the default typeface is always present in the fallback list; - // this is an enhancement from pre-Minikin behavior. - for (int i = 0; i < fontConfig.getFamilies().length; i++) { - FontConfig.Family f = fontConfig.getFamilies()[i]; - if (i == 0 || f.getName() == null) { - FontFamily family = makeFamilyFromParsed(f, bufferForPath); - if (family != null) { - familyList.add(family); - } + final FileInputStream fontsIn = new FileInputStream(xmlPath); + final FontConfig fontConfig = FontListParser.parse(fontsIn); + + final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>(); + final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies(); + + final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>(); + // First traverse families which have a 'name' attribute to create fallback map. + for (final FontConfig.Family xmlFamily : xmlFamilies) { + final String familyName = xmlFamily.getName(); + if (familyName == null) { + continue; + } + final FontFamily family = createFontFamily( + xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()), + xmlFamily.getLanguage(), xmlFamily.getVariant(), bufferCache, fontDir); + if (family == null) { + continue; } + final ArrayList<FontFamily> fallback = new ArrayList<>(); + fallback.add(family); + fallbackListMap.put(familyName, fallback); } - sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]); - setDefault(Typeface.createFromFamilies(sFallbackFonts)); - - Map<String, Typeface> systemFonts = new HashMap<String, Typeface>(); - for (int i = 0; i < fontConfig.getFamilies().length; i++) { - Typeface typeface; - FontConfig.Family f = fontConfig.getFamilies()[i]; - if (f.getName() != null) { - if (i == 0) { - // The first entry is the default typeface; no sense in - // duplicating the corresponding FontFamily. - typeface = sDefaultTypeface; - } else { - FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath); - if (fontFamily == null) { - continue; - } - FontFamily[] families = { fontFamily }; - typeface = Typeface.createFromFamiliesWithDefault(families, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); - } - systemFonts.put(f.getName(), typeface); + + // Then, add fallback fonts to the each fallback map. + for (int i = 0; i < xmlFamilies.length; i++) { + final FontConfig.Family xmlFamily = xmlFamilies[i]; + // The first family (usually the sans-serif family) is always placed immediately + // after the primary family in the fallback. + if (i == 0 || xmlFamily.getName() == null) { + pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, fontDir); + } + } + + // Build the font map and fallback map. + for (int i = 0; i < fallbackListMap.size(); i++) { + final String fallbackName = fallbackListMap.keyAt(i); + final List<FontFamily> familyList = fallbackListMap.valueAt(i); + final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]); + + fallbackMap.put(fallbackName, families); + final long[] ptrArray = new long[families.length]; + for (int j = 0; j < families.length; j++) { + ptrArray[j] = families[j].mNativePtr; } + fontMap.put(fallbackName, new Typeface(nativeCreateFromArray( + ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE))); } - for (FontConfig.Alias alias : fontConfig.getAliases()) { - Typeface base = systemFonts.get(alias.getToName()); + + // Insert alias to font maps. + for (final FontConfig.Alias alias : fontConfig.getAliases()) { + Typeface base = fontMap.get(alias.getToName()); Typeface newFace = base; int weight = alias.getWeight(); if (weight != 400) { newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); } - systemFonts.put(alias.getName(), newFace); + fontMap.put(alias.getName(), newFace); } - sSystemFontMap = systemFonts; - } catch (RuntimeException e) { Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e); // TODO: normal in non-Minikin case, remove or make error when Minikin-only } catch (FileNotFoundException e) { - Log.e(TAG, "Error opening " + configFilename, e); + Log.e(TAG, "Error opening " + xmlPath, e); } catch (IOException e) { - Log.e(TAG, "Error reading " + configFilename, e); + Log.e(TAG, "Error reading " + xmlPath, e); } catch (XmlPullParserException e) { - Log.e(TAG, "XML parse exception for " + configFilename, e); + Log.e(TAG, "XML parse exception for " + xmlPath, e); } } static { - init(); + final ArrayMap<String, Typeface> systemFontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>(); + buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", systemFontMap, + systemFallbackMap); + sSystemFontMap = Collections.unmodifiableMap(systemFontMap); + sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap); + + setDefault(sSystemFontMap.get(DEFAULT_FAMILY)); + // Set up defaults and typefaces exposed in public API DEFAULT = create((String) null, 0); DEFAULT_BOLD = create((String) null, Typeface.BOLD); @@ -1008,10 +1090,6 @@ public class Typeface { } - private static File getSystemFontConfigLocation() { - return new File("/system/etc/"); - } - @Override protected void finalize() throws Throwable { try { |
