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