• 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 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