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.content.res.AssetFileDescriptor; 23 import android.content.res.AssetManager; 24 import android.content.res.Resources; 25 import android.os.LocaleList; 26 import android.os.ParcelFileDescriptor; 27 import android.util.TypedValue; 28 29 import com.android.internal.util.Preconditions; 30 31 import dalvik.annotation.optimization.CriticalNative; 32 33 import libcore.util.NativeAllocationRegistry; 34 35 import java.io.File; 36 import java.io.FileInputStream; 37 import java.io.FileNotFoundException; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.nio.ByteBuffer; 41 import java.nio.ByteOrder; 42 import java.nio.channels.FileChannel; 43 import java.util.Arrays; 44 import java.util.Objects; 45 46 /** 47 * A font class can be used for creating FontFamily. 48 */ 49 public final class Font { 50 private static final String TAG = "Font"; 51 52 private static final int NOT_SPECIFIED = -1; 53 private static final int STYLE_ITALIC = 1; 54 private static final int STYLE_NORMAL = 0; 55 56 /** 57 * A builder class for creating new Font. 58 */ 59 public static final class Builder { 60 private static final NativeAllocationRegistry sFontRegistry = 61 NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(), 62 nGetReleaseNativeFont()); 63 64 private @Nullable ByteBuffer mBuffer; 65 private @Nullable File mFile; 66 private @NonNull String mLocaleList = ""; 67 private @IntRange(from = -1, to = 1000) int mWeight = NOT_SPECIFIED; 68 private @IntRange(from = -1, to = 1) int mItalic = NOT_SPECIFIED; 69 private @IntRange(from = 0) int mTtcIndex = 0; 70 private @Nullable FontVariationAxis[] mAxes = null; 71 private @Nullable IOException mException; 72 73 /** 74 * Constructs a builder with a byte buffer. 75 * 76 * Note that only direct buffer can be used as the source of font data. 77 * 78 * @see ByteBuffer#allocateDirect(int) 79 * @param buffer a byte buffer of a font data 80 */ Builder(@onNull ByteBuffer buffer)81 public Builder(@NonNull ByteBuffer buffer) { 82 Preconditions.checkNotNull(buffer, "buffer can not be null"); 83 if (!buffer.isDirect()) { 84 throw new IllegalArgumentException( 85 "Only direct buffer can be used as the source of font data."); 86 } 87 mBuffer = buffer; 88 } 89 90 /** 91 * Construct a builder with a byte buffer and file path. 92 * 93 * This method is intended to be called only from SystemFonts. 94 * @hide 95 */ Builder(@onNull ByteBuffer buffer, @NonNull File path, @NonNull String localeList)96 public Builder(@NonNull ByteBuffer buffer, @NonNull File path, 97 @NonNull String localeList) { 98 this(buffer); 99 mFile = path; 100 mLocaleList = localeList; 101 } 102 103 /** 104 * Constructs a builder with a file path. 105 * 106 * @param path a file path to the font file 107 */ Builder(@onNull File path)108 public Builder(@NonNull File path) { 109 Preconditions.checkNotNull(path, "path can not be null"); 110 try (FileInputStream fis = new FileInputStream(path)) { 111 final FileChannel fc = fis.getChannel(); 112 mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); 113 } catch (IOException e) { 114 mException = e; 115 } 116 mFile = path; 117 } 118 119 /** 120 * Constructs a builder with a file descriptor. 121 * 122 * @param fd a file descriptor 123 */ Builder(@onNull ParcelFileDescriptor fd)124 public Builder(@NonNull ParcelFileDescriptor fd) { 125 this(fd, 0, -1); 126 } 127 128 /** 129 * Constructs a builder with a file descriptor. 130 * 131 * @param fd a file descriptor 132 * @param offset an offset to of the font data in the file 133 * @param size a size of the font data. If -1 is passed, use until end of the file. 134 */ Builder(@onNull ParcelFileDescriptor fd, @IntRange(from = 0) long offset, @IntRange(from = -1) long size)135 public Builder(@NonNull ParcelFileDescriptor fd, @IntRange(from = 0) long offset, 136 @IntRange(from = -1) long size) { 137 try (FileInputStream fis = new FileInputStream(fd.getFileDescriptor())) { 138 final FileChannel fc = fis.getChannel(); 139 size = (size == -1) ? fc.size() - offset : size; 140 mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, offset, size); 141 } catch (IOException e) { 142 mException = e; 143 } 144 } 145 146 /** 147 * Constructs a builder from an asset manager and a file path in an asset directory. 148 * 149 * @param am the application's asset manager 150 * @param path the file name of the font data in the asset directory 151 */ Builder(@onNull AssetManager am, @NonNull String path)152 public Builder(@NonNull AssetManager am, @NonNull String path) { 153 try { 154 mBuffer = createBuffer(am, path, true /* is asset */, 0 /* cookie */); 155 } catch (IOException e) { 156 mException = e; 157 } 158 } 159 160 /** 161 * Constructs a builder from an asset manager and a file path in an asset directory. 162 * 163 * @param am the application's asset manager 164 * @param path the file name of the font data in the asset directory 165 * @param isAsset true if the undelying data is in asset 166 * @param cookie set asset cookie 167 * @hide 168 */ Builder(@onNull AssetManager am, @NonNull String path, boolean isAsset, int cookie)169 public Builder(@NonNull AssetManager am, @NonNull String path, boolean isAsset, 170 int cookie) { 171 try { 172 mBuffer = createBuffer(am, path, isAsset, cookie); 173 } catch (IOException e) { 174 mException = e; 175 } 176 } 177 178 /** 179 * Constructs a builder from resources. 180 * 181 * Resource ID must points the font file. XML font can not be used here. 182 * 183 * @param res the resource of this application. 184 * @param resId the resource ID of font file. 185 */ Builder(@onNull Resources res, int resId)186 public Builder(@NonNull Resources res, int resId) { 187 final TypedValue value = new TypedValue(); 188 res.getValue(resId, value, true); 189 if (value.string == null) { 190 mException = new FileNotFoundException(resId + " not found"); 191 return; 192 } 193 final String str = value.string.toString(); 194 if (str.toLowerCase().endsWith(".xml")) { 195 mException = new FileNotFoundException(resId + " must be font file."); 196 return; 197 } 198 199 try { 200 mBuffer = createBuffer(res.getAssets(), str, false, value.assetCookie); 201 } catch (IOException e) { 202 mException = e; 203 } 204 } 205 206 /** 207 * Creates a buffer containing font data using the assetManager and other 208 * provided inputs. 209 * 210 * @param am the application's asset manager 211 * @param path the file name of the font data in the asset directory 212 * @param isAsset true if the undelying data is in asset 213 * @param cookie set asset cookie 214 * @return buffer containing the contents of the file 215 * 216 * @hide 217 */ createBuffer(@onNull AssetManager am, @NonNull String path, boolean isAsset, int cookie)218 public static ByteBuffer createBuffer(@NonNull AssetManager am, @NonNull String path, 219 boolean isAsset, int cookie) throws IOException { 220 Preconditions.checkNotNull(am, "assetManager can not be null"); 221 Preconditions.checkNotNull(path, "path can not be null"); 222 223 // Attempt to open as FD, which should work unless the asset is compressed 224 AssetFileDescriptor assetFD; 225 try { 226 if (isAsset) { 227 assetFD = am.openFd(path); 228 } else if (cookie > 0) { 229 assetFD = am.openNonAssetFd(cookie, path); 230 } else { 231 assetFD = am.openNonAssetFd(path); 232 } 233 234 try (FileInputStream fis = assetFD.createInputStream()) { 235 final FileChannel fc = fis.getChannel(); 236 long startOffset = assetFD.getStartOffset(); 237 long declaredLength = assetFD.getDeclaredLength(); 238 return fc.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength); 239 } 240 } catch (IOException e) { 241 // failed to open as FD so now we will attempt to open as an input stream 242 } 243 244 try (InputStream assetStream = isAsset ? am.open(path, AssetManager.ACCESS_BUFFER) 245 : am.openNonAsset(cookie, path, AssetManager.ACCESS_BUFFER)) { 246 247 int capacity = assetStream.available(); 248 ByteBuffer buffer = ByteBuffer.allocateDirect(capacity); 249 buffer.order(ByteOrder.nativeOrder()); 250 assetStream.read(buffer.array(), buffer.arrayOffset(), assetStream.available()); 251 252 if (assetStream.read() != -1) { 253 throw new IOException("Unable to access full contents of " + path); 254 } 255 256 return buffer; 257 } 258 } 259 260 /** 261 * Sets weight of the font. 262 * 263 * Tells the system the weight of the given font. If this function is not called, the system 264 * will resolve the weight value by reading font tables. 265 * 266 * Here are pairs of the common names and their values. 267 * <p> 268 * <table> 269 * <thead> 270 * <tr> 271 * <th align="center">Value</th> 272 * <th align="center">Name</th> 273 * <th align="center">Android Definition</th> 274 * </tr> 275 * </thead> 276 * <tbody> 277 * <tr> 278 * <td align="center">100</td> 279 * <td align="center">Thin</td> 280 * <td align="center">{@link FontStyle#FONT_WEIGHT_THIN}</td> 281 * </tr> 282 * <tr> 283 * <td align="center">200</td> 284 * <td align="center">Extra Light (Ultra Light)</td> 285 * <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_LIGHT}</td> 286 * </tr> 287 * <tr> 288 * <td align="center">300</td> 289 * <td align="center">Light</td> 290 * <td align="center">{@link FontStyle#FONT_WEIGHT_LIGHT}</td> 291 * </tr> 292 * <tr> 293 * <td align="center">400</td> 294 * <td align="center">Normal (Regular)</td> 295 * <td align="center">{@link FontStyle#FONT_WEIGHT_NORMAL}</td> 296 * </tr> 297 * <tr> 298 * <td align="center">500</td> 299 * <td align="center">Medium</td> 300 * <td align="center">{@link FontStyle#FONT_WEIGHT_MEDIUM}</td> 301 * </tr> 302 * <tr> 303 * <td align="center">600</td> 304 * <td align="center">Semi Bold (Demi Bold)</td> 305 * <td align="center">{@link FontStyle#FONT_WEIGHT_SEMI_BOLD}</td> 306 * </tr> 307 * <tr> 308 * <td align="center">700</td> 309 * <td align="center">Bold</td> 310 * <td align="center">{@link FontStyle#FONT_WEIGHT_BOLD}</td> 311 * </tr> 312 * <tr> 313 * <td align="center">800</td> 314 * <td align="center">Extra Bold (Ultra Bold)</td> 315 * <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_BOLD}</td> 316 * </tr> 317 * <tr> 318 * <td align="center">900</td> 319 * <td align="center">Black (Heavy)</td> 320 * <td align="center">{@link FontStyle#FONT_WEIGHT_BLACK}</td> 321 * </tr> 322 * </tbody> 323 * </p> 324 * 325 * @see FontStyle#FONT_WEIGHT_THIN 326 * @see FontStyle#FONT_WEIGHT_EXTRA_LIGHT 327 * @see FontStyle#FONT_WEIGHT_LIGHT 328 * @see FontStyle#FONT_WEIGHT_NORMAL 329 * @see FontStyle#FONT_WEIGHT_MEDIUM 330 * @see FontStyle#FONT_WEIGHT_SEMI_BOLD 331 * @see FontStyle#FONT_WEIGHT_BOLD 332 * @see FontStyle#FONT_WEIGHT_EXTRA_BOLD 333 * @see FontStyle#FONT_WEIGHT_BLACK 334 * @param weight a weight value 335 * @return this builder 336 */ setWeight( @ntRangefrom = FontStyle.FONT_WEIGHT_MIN, to = FontStyle.FONT_WEIGHT_MAX) int weight)337 public @NonNull Builder setWeight( 338 @IntRange(from = FontStyle.FONT_WEIGHT_MIN, to = FontStyle.FONT_WEIGHT_MAX) 339 int weight) { 340 Preconditions.checkArgument( 341 FontStyle.FONT_WEIGHT_MIN <= weight && weight <= FontStyle.FONT_WEIGHT_MAX); 342 mWeight = weight; 343 return this; 344 } 345 346 /** 347 * Sets italic information of the font. 348 * 349 * Tells the system the style of the given font. If this function is not called, the system 350 * will resolve the style by reading font tables. 351 * 352 * For example, if you want to use italic font as upright font, call {@code 353 * setSlant(FontStyle.FONT_SLANT_UPRIGHT)} explicitly. 354 * 355 * @return this builder 356 */ setSlant(@ontStyle.FontSlant int slant)357 public @NonNull Builder setSlant(@FontStyle.FontSlant int slant) { 358 mItalic = slant == FontStyle.FONT_SLANT_UPRIGHT ? STYLE_NORMAL : STYLE_ITALIC; 359 return this; 360 } 361 362 /** 363 * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}. 364 * 365 * @param ttcIndex An index of the font collection. If the font source is not font 366 * collection, do not call this method or specify 0. 367 * @return this builder 368 */ setTtcIndex(@ntRangefrom = 0) int ttcIndex)369 public @NonNull Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) { 370 mTtcIndex = ttcIndex; 371 return this; 372 } 373 374 /** 375 * Sets the font variation settings. 376 * 377 * @param variationSettings see {@link FontVariationAxis#fromFontVariationSettings(String)} 378 * @return this builder 379 * @throws IllegalArgumentException If given string is not a valid font variation settings 380 * format. 381 */ setFontVariationSettings(@ullable String variationSettings)382 public @NonNull Builder setFontVariationSettings(@Nullable String variationSettings) { 383 mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings); 384 return this; 385 } 386 387 /** 388 * Sets the font variation settings. 389 * 390 * @param axes an array of font variation axis tag-value pairs 391 * @return this builder 392 */ setFontVariationSettings(@ullable FontVariationAxis[] axes)393 public @NonNull Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) { 394 mAxes = axes == null ? null : axes.clone(); 395 return this; 396 } 397 398 /** 399 * Creates the font based on the configured values. 400 * @return the Font object 401 */ build()402 public @NonNull Font build() throws IOException { 403 if (mException != null) { 404 throw new IOException("Failed to read font contents", mException); 405 } 406 if (mWeight == NOT_SPECIFIED || mItalic == NOT_SPECIFIED) { 407 final int packed = FontFileUtil.analyzeStyle(mBuffer, mTtcIndex, mAxes); 408 if (FontFileUtil.isSuccess(packed)) { 409 if (mWeight == NOT_SPECIFIED) { 410 mWeight = FontFileUtil.unpackWeight(packed); 411 } 412 if (mItalic == NOT_SPECIFIED) { 413 mItalic = FontFileUtil.unpackItalic(packed) ? STYLE_ITALIC : STYLE_NORMAL; 414 } 415 } else { 416 mWeight = 400; 417 mItalic = STYLE_NORMAL; 418 } 419 } 420 mWeight = Math.max(FontStyle.FONT_WEIGHT_MIN, 421 Math.min(FontStyle.FONT_WEIGHT_MAX, mWeight)); 422 final boolean italic = (mItalic == STYLE_ITALIC); 423 final int slant = (mItalic == STYLE_ITALIC) 424 ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT; 425 final long builderPtr = nInitBuilder(); 426 if (mAxes != null) { 427 for (FontVariationAxis axis : mAxes) { 428 nAddAxis(builderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue()); 429 } 430 } 431 final ByteBuffer readonlyBuffer = mBuffer.asReadOnlyBuffer(); 432 final String filePath = mFile == null ? "" : mFile.getAbsolutePath(); 433 final long ptr = nBuild(builderPtr, readonlyBuffer, filePath, mWeight, italic, 434 mTtcIndex); 435 final Font font = new Font(ptr, readonlyBuffer, mFile, 436 new FontStyle(mWeight, slant), mTtcIndex, mAxes, mLocaleList); 437 sFontRegistry.registerNativeAllocation(font, ptr); 438 return font; 439 } 440 441 /** 442 * Native methods for creating Font 443 */ nInitBuilder()444 private static native long nInitBuilder(); 445 @CriticalNative nAddAxis(long builderPtr, int tag, float value)446 private static native void nAddAxis(long builderPtr, int tag, float value); nBuild( long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath, int weight, boolean italic, int ttcIndex)447 private static native long nBuild( 448 long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath, int weight, 449 boolean italic, int ttcIndex); 450 @CriticalNative nGetReleaseNativeFont()451 private static native long nGetReleaseNativeFont(); 452 } 453 454 private final long mNativePtr; // address of the shared ptr of minikin::Font 455 private final @NonNull ByteBuffer mBuffer; 456 private final @Nullable File mFile; 457 private final FontStyle mFontStyle; 458 private final @IntRange(from = 0) int mTtcIndex; 459 private final @Nullable FontVariationAxis[] mAxes; 460 private final @NonNull String mLocaleList; 461 462 /** 463 * Use Builder instead 464 */ Font(long nativePtr, @NonNull ByteBuffer buffer, @Nullable File file, @NonNull FontStyle fontStyle, @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] axes, @NonNull String localeList)465 private Font(long nativePtr, @NonNull ByteBuffer buffer, @Nullable File file, 466 @NonNull FontStyle fontStyle, @IntRange(from = 0) int ttcIndex, 467 @Nullable FontVariationAxis[] axes, @NonNull String localeList) { 468 mBuffer = buffer; 469 mFile = file; 470 mFontStyle = fontStyle; 471 mNativePtr = nativePtr; 472 mTtcIndex = ttcIndex; 473 mAxes = axes; 474 mLocaleList = localeList; 475 } 476 477 /** 478 * Returns a font file buffer. 479 * 480 * @return a font buffer 481 */ getBuffer()482 public @NonNull ByteBuffer getBuffer() { 483 return mBuffer; 484 } 485 486 /** 487 * Returns a file path of this font. 488 * 489 * This returns null if this font is not created from regular file. 490 * 491 * @return a file path of the font 492 */ getFile()493 public @Nullable File getFile() { 494 return mFile; 495 } 496 497 /** 498 * Get a style associated with this font. 499 * 500 * @see Builder#setWeight(int) 501 * @see Builder#setSlant(int) 502 * @return a font style 503 */ getStyle()504 public @NonNull FontStyle getStyle() { 505 return mFontStyle; 506 } 507 508 /** 509 * Get a TTC index value associated with this font. 510 * 511 * If TTF/OTF file is provided, this value is always 0. 512 * 513 * @see Builder#setTtcIndex(int) 514 * @return a TTC index value 515 */ getTtcIndex()516 public @IntRange(from = 0) int getTtcIndex() { 517 return mTtcIndex; 518 } 519 520 /** 521 * Get a font variation settings associated with this font 522 * 523 * @see Builder#setFontVariationSettings(String) 524 * @see Builder#setFontVariationSettings(FontVariationAxis[]) 525 * @return font variation settings 526 */ getAxes()527 public @Nullable FontVariationAxis[] getAxes() { 528 return mAxes == null ? null : mAxes.clone(); 529 } 530 531 /** 532 * Get a locale list of this font. 533 * 534 * This is always empty if this font is not a system font. 535 * @return a locale list 536 */ getLocaleList()537 public @NonNull LocaleList getLocaleList() { 538 return LocaleList.forLanguageTags(mLocaleList); 539 } 540 541 /** @hide */ getNativePtr()542 public long getNativePtr() { 543 return mNativePtr; 544 } 545 546 @Override equals(@ullable Object o)547 public boolean equals(@Nullable Object o) { 548 if (o == this) { 549 return true; 550 } 551 if (o == null || !(o instanceof Font)) { 552 return false; 553 } 554 Font f = (Font) o; 555 return mFontStyle.equals(f.mFontStyle) && f.mTtcIndex == mTtcIndex 556 && Arrays.equals(f.mAxes, mAxes) && f.mBuffer.equals(mBuffer) 557 && Objects.equals(f.mLocaleList, mLocaleList); 558 } 559 560 @Override hashCode()561 public int hashCode() { 562 return Objects.hash(mFontStyle, mTtcIndex, Arrays.hashCode(mAxes), mBuffer, mLocaleList); 563 } 564 565 @Override toString()566 public String toString() { 567 return "Font {" 568 + "path=" + mFile 569 + ", style=" + mFontStyle 570 + ", ttcIndex=" + mTtcIndex 571 + ", axes=" + FontVariationAxis.toFontVariationSettings(mAxes) 572 + ", localeList=" + mLocaleList 573 + ", buffer=" + mBuffer 574 + "}"; 575 } 576 } 577