1 /*
2  * Copyright (C) 2018 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.fonts;
18 
19 import com.android.ide.common.rendering.api.LayoutLog;
20 import com.android.layoutlib.bridge.Bridge;
21 import com.android.layoutlib.bridge.impl.DelegateManager;
22 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.graphics.FontFamily_Delegate.FontInfo;
27 import android.graphics.FontFamily_Delegate.FontVariant;
28 import android.graphics.Paint;
29 
30 import java.awt.Font;
31 import java.io.ByteArrayInputStream;
32 import java.io.File;
33 import java.nio.ByteBuffer;
34 import java.util.LinkedHashMap;
35 import java.util.Map;
36 
37 import libcore.util.NativeAllocationRegistry_Delegate;
38 
39 import static android.graphics.FontFamily_Delegate.computeMatch;
40 import static android.graphics.FontFamily_Delegate.deriveFont;
41 
42 /**
43  * Delegate implementing the native methods of android.graphics.fonts.FontFamily$Builder
44  * <p>
45  * Through the layoutlib_create tool, the original native methods of FontFamily$Builder have been
46  * replaced by calls to methods of the same name in this delegate class.
47  * <p>
48  * This class behaves like the original native implementation, but in Java, keeping previously
49  * native data into its own objects and mapping them to int that are sent back and forth between it
50  * and the original FontFamily$Builder class.
51  *
52  * @see DelegateManager
53  */
54 public class FontFamily_Builder_Delegate {
55     private static final DelegateManager<FontFamily_Builder_Delegate> sBuilderManager =
56             new DelegateManager<>(FontFamily_Builder_Delegate.class);
57 
58     private static long sFontFamilyFinalizer = -1;
59 
60     // Order does not really matter but we use a LinkedHashMap to get reproducible results across
61     // render calls
62     private Map<FontInfo, Font> mFonts = new LinkedHashMap<>();
63     /**
64      * The variant of the Font Family - compact or elegant.
65      * <p/>
66      * 0 is unspecified, 1 is compact and 2 is elegant. This needs to be kept in sync with values in
67      * android.graphics.FontFamily
68      *
69      * @see Paint#setElegantTextHeight(boolean)
70      */
71     private FontVariant mVariant;
72     private boolean mIsCustomFallback;
73 
74     @LayoutlibDelegate
nInitBuilder()75     /*package*/ static long nInitBuilder() {
76         return sBuilderManager.addNewDelegate(new FontFamily_Builder_Delegate());
77     }
78 
79     @LayoutlibDelegate
nAddFont(long builderPtr, long fontPtr)80     /*package*/ static void nAddFont(long builderPtr, long fontPtr) {
81         FontFamily_Builder_Delegate familyBuilder = sBuilderManager.getDelegate(builderPtr);
82         Font_Builder_Delegate fontBuilder = Font_Builder_Delegate.sBuilderManager.getDelegate(fontPtr);
83         if (familyBuilder == null || fontBuilder == null) {
84             return;
85         }
86         Font font;
87         if (fontBuilder.filePath.equals("")) {
88             font = loadFontBuffer(fontBuilder.mBuffer);
89 
90         } else {
91             font = loadFontPath(fontBuilder.filePath);
92         }
93         if (font != null) {
94             familyBuilder.addFont(font, fontBuilder.mWeight, fontBuilder.mItalic);
95         }
96     }
97 
98     @LayoutlibDelegate
nBuild(long builderPtr, String langTags, int variant, boolean isCustomFallback)99     /*package*/ static long nBuild(long builderPtr, String langTags, int variant,
100             boolean isCustomFallback) {
101         FontFamily_Builder_Delegate builder = sBuilderManager.getDelegate(builderPtr);
102         if (builder != null) {
103             assert variant < 3;
104             builder.mVariant = FontVariant.values()[variant];
105             builder.mIsCustomFallback = isCustomFallback;
106         }
107         return builderPtr;
108     }
109 
110     @LayoutlibDelegate
111     /*package*/ static long nGetReleaseNativeFamily() {
112         synchronized (Font_Builder_Delegate.class) {
113             if (sFontFamilyFinalizer == -1) {
114                 sFontFamilyFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
115                         sBuilderManager::removeJavaReferenceFor);
116             }
117         }
118         return sFontFamilyFinalizer;
119     }
120 
121     public static FontFamily_Builder_Delegate getDelegate(long nativeFontFamily) {
122         return sBuilderManager.getDelegate(nativeFontFamily);
123     }
124 
125     @Nullable
126     public Font getFont(int desiredWeight, boolean isItalic) {
127         FontInfo desiredStyle = new FontInfo();
128         desiredStyle.mWeight = desiredWeight;
129         desiredStyle.mIsItalic = isItalic;
130 
131         Font cachedFont = mFonts.get(desiredStyle);
132         if (cachedFont != null) {
133             return cachedFont;
134         }
135 
136         FontInfo bestFont = null;
137 
138         if (mFonts.size() == 1) {
139             // No need to compute the match since we only have one candidate
140             bestFont = mFonts.keySet().iterator().next();
141         } else {
142             int bestMatch = Integer.MAX_VALUE;
143 
144             for (FontInfo font : mFonts.keySet()) {
145                 int match = computeMatch(font, desiredStyle);
146                 if (match < bestMatch) {
147                     bestMatch = match;
148                     bestFont = font;
149                     if (bestMatch == 0) {
150                         break;
151                     }
152                 }
153             }
154         }
155 
156         if (bestFont == null) {
157             return null;
158         }
159 
160 
161         // Derive the font as required and add it to the list of Fonts.
162         deriveFont(bestFont, desiredStyle);
163         addFont(desiredStyle);
164         return desiredStyle.mFont;
165     }
166 
167     public FontVariant getVariant() {
168         return mVariant;
169     }
170 
171     // ---- private helper methods ----
172 
173     private void addFont(@NonNull Font font, int weight, boolean italic) {
174         FontInfo fontInfo = new FontInfo();
175         fontInfo.mFont = font;
176         fontInfo.mWeight = weight;
177         fontInfo.mIsItalic = italic;
178         addFont(fontInfo);
179     }
180 
181     private void addFont(@NonNull FontInfo fontInfo) {
182         mFonts.putIfAbsent(fontInfo, fontInfo.mFont);
183     }
184 
185     private static Font loadFontBuffer(@NonNull ByteBuffer buffer) {
186         try {
187             byte[] byteArray = new byte[buffer.limit()];
188             buffer.rewind();
189             buffer.get(byteArray);
190             return Font.createFont(Font.TRUETYPE_FONT, new ByteArrayInputStream(byteArray));
191         } catch (Exception e) {
192             Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, "Unable to load font",
193                     e, null, null);
194         }
195 
196         return null;
197     }
198 
199     private static Font loadFontPath(@NonNull String path) {
200         try {
201             File file = new File(path);
202             return Font.createFont(Font.TRUETYPE_FONT, file);
203         } catch (Exception e) {
204             Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, "Unable to load font",
205                     e, null, null);
206         }
207 
208         return null;
209     }
210 }
211