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