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