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 android.annotation.IntRange; 20 import android.annotation.NonNull; 21 import android.text.FontConfig; 22 23 import com.android.internal.util.Preconditions; 24 25 import dalvik.annotation.optimization.CriticalNative; 26 27 import libcore.util.NativeAllocationRegistry; 28 29 import java.util.ArrayList; 30 import java.util.HashSet; 31 32 /** 33 * A font family class can be used for creating Typeface. 34 * 35 * <p> 36 * A font family is a bundle of fonts for drawing text in various styles. 37 * For example, you can bundle regular style font and bold style font into a single font family, 38 * then system will select the correct style font from family for drawing. 39 * 40 * <pre> 41 * FontFamily family = new FontFamily.Builder(new Font.Builder("regular.ttf").build()) 42 * .addFont(new Font.Builder("bold.ttf").build()).build(); 43 * Typeface typeface = new Typeface.Builder2(family).build(); 44 * 45 * SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World."); 46 * ssb.setSpan(new StyleSpan(Typeface.Bold), 6, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 47 * 48 * textView.setTypeface(typeface); 49 * textView.setText(ssb); 50 * </pre> 51 * 52 * In this example, "Hello, " is drawn with "regular.ttf", and "World." is drawn with "bold.ttf". 53 * 54 * If there is no font exactly matches with the text style, the system will select the closest font. 55 * </p> 56 * 57 */ 58 public final class FontFamily { 59 private static final String TAG = "FontFamily"; 60 61 /** 62 * A builder class for creating new FontFamily. 63 */ 64 public static final class Builder { 65 private static final NativeAllocationRegistry sFamilyRegistory = 66 NativeAllocationRegistry.createMalloced(FontFamily.class.getClassLoader(), 67 nGetReleaseNativeFamily()); 68 69 private final ArrayList<Font> mFonts = new ArrayList<>(); 70 private final HashSet<Integer> mStyleHashSet = new HashSet<>(); 71 72 /** 73 * Constructs a builder. 74 * 75 * @param font a font 76 */ Builder(@onNull Font font)77 public Builder(@NonNull Font font) { 78 Preconditions.checkNotNull(font, "font can not be null"); 79 mStyleHashSet.add(makeStyleIdentifier(font)); 80 mFonts.add(font); 81 } 82 83 /** 84 * Adds different style font to the builder. 85 * 86 * System will select the font if the text style is closest to the font. 87 * If the same style font is already added to the builder, this method will fail with 88 * {@link IllegalArgumentException}. 89 * 90 * Note that system assumes all fonts bundled in FontFamily have the same coverage for the 91 * code points. For example, regular style font and bold style font must have the same code 92 * point coverage, otherwise some character may be shown as tofu. 93 * 94 * @param font a font 95 * @return this builder 96 */ addFont(@onNull Font font)97 public @NonNull Builder addFont(@NonNull Font font) { 98 Preconditions.checkNotNull(font, "font can not be null"); 99 if (!mStyleHashSet.add(makeStyleIdentifier(font))) { 100 throw new IllegalArgumentException(font + " has already been added"); 101 } 102 mFonts.add(font); 103 return this; 104 } 105 106 /** 107 * Build the font family 108 * @return a font family 109 */ build()110 public @NonNull FontFamily build() { 111 return build("", FontConfig.Family.VARIANT_DEFAULT, true /* isCustomFallback */); 112 } 113 114 /** @hide */ build(@onNull String langTags, int variant, boolean isCustomFallback)115 public @NonNull FontFamily build(@NonNull String langTags, int variant, 116 boolean isCustomFallback) { 117 final long builderPtr = nInitBuilder(); 118 for (int i = 0; i < mFonts.size(); ++i) { 119 nAddFont(builderPtr, mFonts.get(i).getNativePtr()); 120 } 121 final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback); 122 final FontFamily family = new FontFamily(mFonts, ptr); 123 sFamilyRegistory.registerNativeAllocation(family, ptr); 124 return family; 125 } 126 makeStyleIdentifier(@onNull Font font)127 private static int makeStyleIdentifier(@NonNull Font font) { 128 return font.getStyle().getWeight() | (font.getStyle().getSlant() << 16); 129 } 130 nInitBuilder()131 private static native long nInitBuilder(); 132 @CriticalNative nAddFont(long builderPtr, long fontPtr)133 private static native void nAddFont(long builderPtr, long fontPtr); nBuild(long builderPtr, String langTags, int variant, boolean isCustomFallback)134 private static native long nBuild(long builderPtr, String langTags, int variant, 135 boolean isCustomFallback); 136 @CriticalNative nGetReleaseNativeFamily()137 private static native long nGetReleaseNativeFamily(); 138 } 139 140 private final ArrayList<Font> mFonts; 141 private final long mNativePtr; 142 143 // Use Builder instead. FontFamily(@onNull ArrayList<Font> fonts, long ptr)144 private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) { 145 mFonts = fonts; 146 mNativePtr = ptr; 147 } 148 149 /** 150 * Returns a font 151 * 152 * @param index an index of the font 153 * @return a registered font 154 */ getFont(@ntRangefrom = 0) int index)155 public @NonNull Font getFont(@IntRange(from = 0) int index) { 156 return mFonts.get(index); 157 } 158 159 /** 160 * Returns the number of fonts in this FontFamily. 161 * 162 * @return the number of fonts registered in this family. 163 */ getSize()164 public @IntRange(from = 1) int getSize() { 165 return mFonts.size(); 166 } 167 168 /** @hide */ getNativePtr()169 public long getNativePtr() { 170 return mNativePtr; 171 } 172 } 173