• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
20 
21 import static java.lang.annotation.RetentionPolicy.SOURCE;
22 
23 import android.annotation.FlaggedApi;
24 import android.annotation.IntDef;
25 import android.annotation.IntRange;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.annotation.SuppressLint;
29 import android.text.FontConfig;
30 import android.util.SparseIntArray;
31 
32 import com.android.internal.util.Preconditions;
33 
34 import dalvik.annotation.optimization.CriticalNative;
35 import dalvik.annotation.optimization.FastNative;
36 
37 import libcore.util.NativeAllocationRegistry;
38 
39 import java.lang.annotation.Retention;
40 import java.util.ArrayList;
41 import java.util.Set;
42 
43 /**
44  * A font family class can be used for creating Typeface.
45  *
46  * <p>
47  * A font family is a bundle of fonts for drawing text in various styles.
48  * For example, you can bundle regular style font and bold style font into a single font family,
49  * then system will select the correct style font from family for drawing.
50  *
51  * <pre>
52  *  FontFamily family = new FontFamily.Builder(new Font.Builder("regular.ttf").build())
53  *      .addFont(new Font.Builder("bold.ttf").build()).build();
54  *  Typeface typeface = new Typeface.Builder2(family).build();
55  *
56  *  SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World.");
57  *  ssb.setSpan(new StyleSpan(Typeface.Bold), 6, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
58  *
59  *  textView.setTypeface(typeface);
60  *  textView.setText(ssb);
61  * </pre>
62  *
63  * In this example, "Hello, " is drawn with "regular.ttf", and "World." is drawn with "bold.ttf".
64  *
65  * If there is no font exactly matches with the text style, the system will select the closest font.
66  * </p>
67  *
68  */
69 @android.ravenwood.annotation.RavenwoodKeepWholeClass
70 public final class FontFamily {
71 
72     private static final String TAG = "FontFamily";
73 
74     /**
75      * A builder class for creating new FontFamily.
76      */
77     public static final class Builder {
78         private static class NoImagePreloadHolder {
79             private static final NativeAllocationRegistry sFamilyRegistry =
80                     NativeAllocationRegistry.createMalloced(FontFamily.class.getClassLoader(),
81                             nGetReleaseNativeFamily());
82         }
83 
84         private final ArrayList<Font> mFonts = new ArrayList<>();
85         // Most FontFamily only has  regular, bold, italic, bold-italic. Thus 4 should be good for
86         // initial capacity.
87         private final SparseIntArray mStyles = new SparseIntArray(4);
88 
89 
90         /**
91          * Constructs a builder.
92          *
93          * @param font a font
94          */
Builder(@onNull Font font)95         public Builder(@NonNull Font font) {
96             Preconditions.checkNotNull(font, "font can not be null");
97             mStyles.append(makeStyleIdentifier(font), 0);
98             mFonts.add(font);
99         }
100 
101         /**
102          * Adds different style font to the builder.
103          *
104          * System will select the font if the text style is closest to the font.
105          * If the same style font is already added to the builder, this method will fail with
106          * {@link IllegalArgumentException}.
107          *
108          * Note that system assumes all fonts bundled in FontFamily have the same coverage for the
109          * code points. For example, regular style font and bold style font must have the same code
110          * point coverage, otherwise some character may be shown as tofu.
111          *
112          * @param font a font
113          * @return this builder
114          */
addFont(@onNull Font font)115         public @NonNull Builder addFont(@NonNull Font font) {
116             Preconditions.checkNotNull(font, "font can not be null");
117             int key = makeStyleIdentifier(font);
118             if (mStyles.indexOfKey(key) >= 0) {
119                 throw new IllegalArgumentException(font + " has already been added");
120             }
121             mStyles.append(key, 0);
122             mFonts.add(font);
123             return this;
124         }
125 
126         /**
127          * Build a variable font family that automatically adjust the `wght` and `ital` axes value
128          * for the requested weight/italic style values.
129          *
130          * To build a variable font family, added fonts must meet one of following conditions.
131          *
132          * If two font files are added, both font files must support `wght` axis and one font must
133          * support {@link FontStyle#FONT_SLANT_UPRIGHT} and another font must support
134          * {@link FontStyle#FONT_SLANT_ITALIC}. If the requested weight value is lower than minimum
135          * value of the supported `wght` axis, the minimum supported `wght` value is used. If the
136          * requested weight value is larger than maximum value of the supported `wght` axis, the
137          * maximum supported `wght` value is used. The weight values of the fonts are ignored.
138          *
139          * If one font file is added, that font must support the `wght` axis. If that font support
140          * `ital` axis, that `ital` value is set to 1 when the italic style is requested. If that
141          * font doesn't support `ital` axis, synthetic italic may be used. If the requested
142          * weight value is lower than minimum value of the supported `wght` axis, the minimum
143          * supported `wght` value is used. If the requested weight value is larger than maximum
144          * value of the supported `wght`axis, the maximum supported `wght` value is used. The weight
145          * value of the font is ignored.
146          *
147          * If none of the above conditions are met, the provided font files cannot be used for
148          * variable font family and this function returns {@code null}. Even if this function
149          * returns {@code null}, you can still use {@link #build()} method for creating FontFamily
150          * instance with manually specifying variation settings by using
151          * {@link Font.Builder#setFontVariationSettings(String)}.
152          *
153          * @return A variable font family. null if a variable font cannot be built from the given
154          *         fonts.
155          */
156         @SuppressLint("BuilderSetStyle")
157         @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
buildVariableFamily()158         public @Nullable FontFamily buildVariableFamily() {
159             int variableFamilyType = analyzeAndResolveVariableType(mFonts);
160             if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) {
161                 return null;
162             }
163             return build("", FontConfig.FontFamily.VARIANT_DEFAULT,
164                     true /* isCustomFallback */,
165                     false /* isDefaultFallback */,
166                     variableFamilyType);
167         }
168 
169         /**
170          * Build the font family
171          * @return a font family
172          */
build()173         public @NonNull FontFamily build() {
174             return build("", FontConfig.FontFamily.VARIANT_DEFAULT,
175                     true /* isCustomFallback */,
176                     false /* isDefaultFallback */,
177                     VARIABLE_FONT_FAMILY_TYPE_NONE);
178         }
179 
180         /** @hide */
build(@onNull String langTags, int variant, boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType)181         public @NonNull FontFamily build(@NonNull String langTags, int variant,
182                 boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType) {
183 
184             final long builderPtr = nInitBuilder();
185             for (int i = 0; i < mFonts.size(); ++i) {
186                 nAddFont(builderPtr, mFonts.get(i).getNativePtr());
187             }
188             final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback,
189                     isDefaultFallback, variableFamilyType);
190             final FontFamily family = new FontFamily(ptr);
191             NoImagePreloadHolder.sFamilyRegistry.registerNativeAllocation(family, ptr);
192             return family;
193         }
194 
makeStyleIdentifier(@onNull Font font)195         private static int makeStyleIdentifier(@NonNull Font font) {
196             return font.getStyle().getWeight() | (font.getStyle().getSlant()  << 16);
197         }
198 
199         /**
200          * A special variable font family type that indicates `analyzeAndResolveVariableType` could
201          * not be identified the variable font family type.
202          *
203          * @see #buildVariableFamily()
204          * @hide
205          */
206         public static final int VARIABLE_FONT_FAMILY_TYPE_UNKNOWN = -1;
207 
208         /**
209          * A variable font family type that indicates no variable font family can be used.
210          *
211          * The font family is used as bundle of static fonts.
212          * @see #buildVariableFamily()
213          * @hide
214          */
215         public static final int VARIABLE_FONT_FAMILY_TYPE_NONE = 0;
216         /**
217          * A variable font family type that indicates single font file can be used for multiple
218          * weight. For the italic style, fake italic may be applied.
219          *
220          * @see #buildVariableFamily()
221          * @hide
222          */
223         public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY = 1;
224         /**
225          * A variable font family type that indicates single font file can be used for multiple
226          * weight and italic.
227          *
228          * @see #buildVariableFamily()
229          * @hide
230          */
231         public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL = 2;
232         /**
233          * A variable font family type that indicates two font files are included in the family:
234          * one can be used for upright with various weights, the other one can be used for italic
235          * with various weights.
236          *
237          * @see #buildVariableFamily()
238          * @hide
239          */
240         public static final int VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT = 3;
241 
242         /** @hide */
243         @Retention(SOURCE)
244         @IntDef(prefix = { "VARIABLE_FONT_FAMILY_TYPE_" }, value = {
245                 VARIABLE_FONT_FAMILY_TYPE_UNKNOWN,
246                 VARIABLE_FONT_FAMILY_TYPE_NONE,
247                 VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY,
248                 VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL,
249                 VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT
250         })
251         public @interface VariableFontFamilyType {}
252 
253         /**
254          * The registered italic axis used for adjusting requested style.
255          * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_ital
256          */
257         private static final int TAG_ital = 0x6974616C;  // i(0x69), t(0x74), a(0x61), l(0x6c)
258 
259         /**
260          * The registered weight axis used for adjusting requested style.
261          * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_wght
262          */
263         private static final int TAG_wght = 0x77676874;  // w(0x77), g(0x67), h(0x68), t(0x74)
264 
265         /** @hide */
analyzeAndResolveVariableType( ArrayList<Font> fonts)266         public static @VariableFontFamilyType int analyzeAndResolveVariableType(
267                 ArrayList<Font> fonts) {
268             if (fonts.size() > 2) {
269                 return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
270             }
271 
272             if (fonts.size() == 1) {
273                 Font font = fonts.get(0);
274                 Set<Integer> supportedAxes =
275                         FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex());
276                 if (supportedAxes.contains(TAG_wght)) {
277                     if (supportedAxes.contains(TAG_ital)) {
278                         return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
279                     } else {
280                         return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
281                     }
282                 } else {
283                     return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
284                 }
285             } else {
286                 for (int i = 0; i < fonts.size(); ++i) {
287                     Font font = fonts.get(i);
288                     Set<Integer> supportedAxes =
289                             FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex());
290                     if (!supportedAxes.contains(TAG_wght)) {
291                         return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
292                     }
293                 }
294                 boolean italic1 = fonts.get(0).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC;
295                 boolean italic2 = fonts.get(1).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC;
296 
297                 if (italic1 == italic2) {
298                     return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
299                 } else {
300                     if (italic1) {
301                         // Swap fonts to make the first font upright, second font italic.
302                         Font firstFont = fonts.get(0);
303                         fonts.set(0, fonts.get(1));
304                         fonts.set(1, firstFont);
305                     }
306                     return VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
307                 }
308             }
309         }
310 
nInitBuilder()311         private static native long nInitBuilder();
312         @CriticalNative
nAddFont(long builderPtr, long fontPtr)313         private static native void nAddFont(long builderPtr, long fontPtr);
nBuild(long builderPtr, String langTags, int variant, boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType)314         private static native long nBuild(long builderPtr, String langTags, int variant,
315                 boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType);
316         @CriticalNative
nGetReleaseNativeFamily()317         private static native long nGetReleaseNativeFamily();
318     }
319 
320     private final long mNativePtr;
321 
322     // Use Builder instead.
323     /** @hide */
FontFamily(long ptr)324     public FontFamily(long ptr) {
325         mNativePtr = ptr;
326     }
327 
328     /**
329      * Returns a BCP-47 compliant language tags associated with this font family.
330      * @hide
331      * @return a BCP-47 compliant language tag.
332      */
getLangTags()333     public @Nullable String getLangTags() {
334         return nGetLangTags(mNativePtr);
335     }
336 
337     /**
338      * @hide
339      * @return a family variant
340      */
getVariant()341     public int getVariant() {
342         return nGetVariant(mNativePtr);
343     }
344 
345     /**
346      * Returns a font
347      *
348      * @param index an index of the font
349      * @return a registered font
350      */
getFont(@ntRangefrom = 0) int index)351     public @NonNull Font getFont(@IntRange(from = 0) int index) {
352         if (index < 0 || getSize() <= index) {
353             throw new IndexOutOfBoundsException();
354         }
355         return new Font(nGetFont(mNativePtr, index));
356     }
357 
358     /**
359      * Returns the number of fonts in this FontFamily.
360      *
361      * @return the number of fonts registered in this family.
362      */
getSize()363     public @IntRange(from = 1) int getSize() {
364         return nGetFontSize(mNativePtr);
365     }
366 
367     /** @hide */
getNativePtr()368     public long getNativePtr() {
369         return mNativePtr;
370     }
371 
372     @CriticalNative
nGetFontSize(long family)373     private static native int nGetFontSize(long family);
374 
375     @CriticalNative
nGetFont(long family, int i)376     private static native long nGetFont(long family, int i);
377 
378     @FastNative
nGetLangTags(long family)379     private static native String nGetLangTags(long family);
380 
381     @CriticalNative
nGetVariant(long family)382     private static native int nGetVariant(long family);
383 }
384