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