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