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