blob: 06096ff7799aec924c47bea5ef9f323316834b95 [file] [log] [blame]
/*
* 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.LayoutLog;
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.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
* <p>
* 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.
* <p>
* 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<FontFamily_Builder_Delegate> 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<FontInfo, Font> mFonts = new LinkedHashMap<>();
/**
* The variant of the Font Family - compact or elegant.
* <p/>
* 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 builder = sBuilderManager.getDelegate(builderPtr);
Font_Builder_Delegate font = Font_Builder_Delegate.sBuilderManager.getDelegate(fontPtr);
if (builder != null && font != null) {
builder.addFont(font.mBuffer, font.mTtcIndex, font.mWeight, font.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(final ByteBuffer buffer, int ttcIndex, int weight, boolean italic) {
addFont(buffer, weight, italic);
}
private void addFont(@NonNull ByteBuffer buffer, int weight, boolean italic) {
// Set valid to true, even if the font fails to load.
Font font = loadFont(buffer);
if (font == null) {
return;
}
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 loadFont(@NonNull ByteBuffer buffer) {
try {
byte[] byteArray = new byte[buffer.limit()];
buffer.get(byteArray);
buffer.rewind();
return Font.createFont(Font.TRUETYPE_FONT, new ByteArrayInputStream(byteArray));
} catch (Exception e) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, "Unable to load font",
e, null);
}
return null;
}
}