Home | History | Annotate | Download | only in fonts
      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
     74     /*package*/ static long nInitBuilder() {
     75         return sBuilderManager.addNewDelegate(new FontFamily_Builder_Delegate());
     76     }
     77 
     78     @LayoutlibDelegate
     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
     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