• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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