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.ide.common.rendering.api.AndroidConstants;
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.impl.DelegateManager;
26 import com.android.layoutlib.bridge.impl.RenderAction;
27 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
28 
29 import org.xmlpull.v1.XmlPullParser;
30 import org.xmlpull.v1.XmlPullParserException;
31 
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.content.res.FontResourcesParser;
35 
36 import java.io.IOException;
37 import java.nio.file.Files;
38 import java.nio.file.Paths;
39 
40 /**
41  * Delegate implementing the native methods of android.graphics.Typeface
42  * <p>
43  * Through the layoutlib_create tool, the original native methods of Typeface have been replaced by
44  * calls to methods of the same name in this delegate class.
45  * <p>
46  * This class behaves like the original native implementation, but in Java, keeping previously
47  * native data into its own objects and mapping them to int that are sent back and forth between it
48  * and the original Typeface class.
49  *
50  * @see DelegateManager
51  */
52 public final class Typeface_Delegate {
53     /**
54      * Loads a single font or font family from disk
55      */
56     @Nullable
createFromDisk(@onNull BridgeContext context, @NonNull String path, boolean isFramework)57     public static Typeface createFromDisk(@NonNull BridgeContext context, @NonNull String path,
58             boolean isFramework) {
59         // Check if this is an asset that we've already loaded dynamically
60         Typeface typeface = Typeface.findFromCache(context.getAssets(), path);
61         if (typeface != null) {
62             return typeface;
63         }
64 
65         if (path.isBlank()) {
66             return null;
67         }
68 
69         String lowerCaseValue = path.toLowerCase();
70         if (lowerCaseValue.endsWith(AndroidConstants.DOT_XML)) {
71             // create a block parser for the file
72             XmlPullParser parser = context.getLayoutlibCallback().createXmlParserForPsiFile(path);
73 
74             if (parser != null) {
75                 // TODO(b/156609434): The aapt namespace should not matter for parsing font files?
76                 BridgeXmlBlockParser blockParser =
77                         new BridgeXmlBlockParser(
78                                 parser, context, ResourceNamespace.fromBoolean(isFramework));
79                 try {
80                     FontResourcesParser.FamilyResourceEntry entry =
81                             FontResourcesParser.parse(blockParser, context.getResources());
82                     typeface = Typeface.createFromResources(entry, context.getAssets(), path);
83                 } catch (XmlPullParserException | IOException e) {
84                     Bridge.getLog().error(null, "Failed to parse file " + path, e, null,
85                             null /*data*/);
86                 } finally {
87                     blockParser.ensurePopped();
88                 }
89             } else {
90                 Bridge.getLog().error(ILayoutLog.TAG_BROKEN,
91                         String.format("File %s does not exist (or is not a file)", path),
92                         null, null /*data*/);
93             }
94         } else {
95             typeface = new Typeface.Builder(context.getAssets(), path, false, 0).build();
96         }
97 
98         return typeface;
99     }
100 
101     @LayoutlibDelegate
create(String familyName, int style)102     /*package*/ static Typeface create(String familyName, int style) {
103         if (familyName != null && Files.exists(Paths.get(familyName))) {
104             // Workaround for b/64137851
105             // Support lib will call this method after failing to create the TypefaceCompat.
106             return Typeface_Delegate.createFromDisk(RenderAction.getCurrentContext(), familyName,
107                     false);
108         }
109         return Typeface.create_Original(familyName, style);
110     }
111 
112     @LayoutlibDelegate
create(Typeface family, int style)113     /*package*/ static Typeface create(Typeface family, int style) {
114         return Typeface.create_Original(family, style);
115     }
116 
117     @LayoutlibDelegate
create(Typeface family, int style, boolean isItalic)118     /*package*/ static Typeface create(Typeface family, int style, boolean isItalic) {
119         return Typeface.create_Original(family, style, isItalic);
120     }
121 }
122