/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.graphics.fonts; import com.android.ide.common.rendering.api.ILayoutLog; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.FontFamily_Delegate.FontInfo; import android.graphics.FontFamily_Delegate.FontVariant; import android.graphics.Paint; import java.awt.Font; import java.io.ByteArrayInputStream; import java.io.File; import java.nio.ByteBuffer; import java.util.LinkedHashMap; import java.util.Map; import libcore.util.NativeAllocationRegistry_Delegate; import static android.graphics.FontFamily_Delegate.computeMatch; import static android.graphics.FontFamily_Delegate.deriveFont; /** * Delegate implementing the native methods of android.graphics.fonts.FontFamily$Builder *

* Through the layoutlib_create tool, the original native methods of FontFamily$Builder have been * replaced by calls to methods of the same name in this delegate class. *

* This class behaves like the original native implementation, but in Java, keeping previously * native data into its own objects and mapping them to int that are sent back and forth between it * and the original FontFamily$Builder class. * * @see DelegateManager */ public class FontFamily_Builder_Delegate { private static final DelegateManager sBuilderManager = new DelegateManager<>(FontFamily_Builder_Delegate.class); private static long sFontFamilyFinalizer = -1; // Order does not really matter but we use a LinkedHashMap to get reproducible results across // render calls private Map mFonts = new LinkedHashMap<>(); /** * The variant of the Font Family - compact or elegant. *

* 0 is unspecified, 1 is compact and 2 is elegant. This needs to be kept in sync with values in * android.graphics.FontFamily * * @see Paint#setElegantTextHeight(boolean) */ private FontVariant mVariant; private boolean mIsCustomFallback; @LayoutlibDelegate /*package*/ static long nInitBuilder() { return sBuilderManager.addNewDelegate(new FontFamily_Builder_Delegate()); } @LayoutlibDelegate /*package*/ static void nAddFont(long builderPtr, long fontPtr) { FontFamily_Builder_Delegate familyBuilder = sBuilderManager.getDelegate(builderPtr); Font_Builder_Delegate fontBuilder = Font_Builder_Delegate.sBuilderManager.getDelegate(fontPtr); if (familyBuilder == null || fontBuilder == null) { return; } Font font; if (fontBuilder.filePath.equals("")) { font = loadFontBuffer(fontBuilder.mBuffer); } else { font = loadFontPath(fontBuilder.filePath); } if (font != null) { familyBuilder.addFont(font, fontBuilder.mWeight, fontBuilder.mItalic); } } @LayoutlibDelegate /*package*/ static long nBuild(long builderPtr, String langTags, int variant, boolean isCustomFallback) { FontFamily_Builder_Delegate builder = sBuilderManager.getDelegate(builderPtr); if (builder != null) { assert variant < 3; builder.mVariant = FontVariant.values()[variant]; builder.mIsCustomFallback = isCustomFallback; } return builderPtr; } @LayoutlibDelegate /*package*/ static long nGetReleaseNativeFamily() { synchronized (Font_Builder_Delegate.class) { if (sFontFamilyFinalizer == -1) { sFontFamilyFinalizer = NativeAllocationRegistry_Delegate.createFinalizer( sBuilderManager::removeJavaReferenceFor); } } return sFontFamilyFinalizer; } public static FontFamily_Builder_Delegate getDelegate(long nativeFontFamily) { return sBuilderManager.getDelegate(nativeFontFamily); } @Nullable public Font getFont(int desiredWeight, boolean isItalic) { FontInfo desiredStyle = new FontInfo(); desiredStyle.mWeight = desiredWeight; desiredStyle.mIsItalic = isItalic; Font cachedFont = mFonts.get(desiredStyle); if (cachedFont != null) { return cachedFont; } FontInfo bestFont = null; if (mFonts.size() == 1) { // No need to compute the match since we only have one candidate bestFont = mFonts.keySet().iterator().next(); } else { int bestMatch = Integer.MAX_VALUE; for (FontInfo font : mFonts.keySet()) { int match = computeMatch(font, desiredStyle); if (match < bestMatch) { bestMatch = match; bestFont = font; if (bestMatch == 0) { break; } } } } if (bestFont == null) { return null; } // Derive the font as required and add it to the list of Fonts. deriveFont(bestFont, desiredStyle); addFont(desiredStyle); return desiredStyle.mFont; } public FontVariant getVariant() { return mVariant; } // ---- private helper methods ---- private void addFont(@NonNull Font font, int weight, boolean italic) { FontInfo fontInfo = new FontInfo(); fontInfo.mFont = font; fontInfo.mWeight = weight; fontInfo.mIsItalic = italic; addFont(fontInfo); } private void addFont(@NonNull FontInfo fontInfo) { mFonts.putIfAbsent(fontInfo, fontInfo.mFont); } private static Font loadFontBuffer(@NonNull ByteBuffer buffer) { try { byte[] byteArray = new byte[buffer.limit()]; buffer.rewind(); buffer.get(byteArray); return Font.createFont(Font.TRUETYPE_FONT, new ByteArrayInputStream(byteArray)); } catch (Exception e) { Bridge.getLog().fidelityWarning(ILayoutLog.TAG_BROKEN, "Unable to load font", e, null, null); } return null; } private static Font loadFontPath(@NonNull String path) { try { File file = new File(path); return Font.createFont(Font.TRUETYPE_FONT, file); } catch (Exception e) { Bridge.getLog().fidelityWarning(ILayoutLog.TAG_BROKEN, "Unable to load font", e, null, null); } return null; } }