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