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