• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.IntDef;
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.Parcel;
24 import android.os.ParcelFileDescriptor;
25 import android.os.Parcelable;
26 import android.text.FontConfig;
27 import android.util.TypedXmlSerializer;
28 
29 import org.xmlpull.v1.XmlPullParser;
30 import org.xmlpull.v1.XmlPullParserException;
31 
32 import java.io.IOException;
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Objects;
38 
39 /**
40  * Represents a font update request. Currently only font install request is supported.
41  * @hide
42  */
43 public final class FontUpdateRequest implements Parcelable {
44 
45     public static final int TYPE_UPDATE_FONT_FILE = 0;
46     public static final int TYPE_UPDATE_FONT_FAMILY = 1;
47 
48     @IntDef(prefix = "TYPE_", value = {
49             TYPE_UPDATE_FONT_FILE,
50             TYPE_UPDATE_FONT_FAMILY,
51     })
52     @Retention(RetentionPolicy.SOURCE)
53     public @interface Type {}
54 
55     /**
56      * Font object used for update.
57      *
58      * Here is an example of Family/Font XML.
59      * <family name="my-sans">
60      *   <font name="MySans" weight="400" slant="0" axis="'wght' 400 'ital' 0" index="0" />
61      *   <font name="MySans" weight="400" slant="0" axis="'wght' 400 'ital' 1" index="0" />
62      *   <font name="MySans" weight="400" slant="0" axis="'wght' 700 'ital' 0" index="0" />
63      *   <font name="MySans" weight="400" slant="0" axis="'wght' 700 'ital' 1" index="0" />
64      * </family>
65      *
66      * @see Font#readFromXml(XmlPullParser)
67      * @see Font#writeToXml(TypedXmlSerializer, Font)
68      * @see Family#readFromXml(XmlPullParser)
69      * @see Family#writeFamilyToXml(TypedXmlSerializer, Family)
70      */
71     public static final class Font implements Parcelable {
72         private static final String ATTR_INDEX = "index";
73         private static final String ATTR_WEIGHT = "weight";
74         private static final String ATTR_SLANT = "slant";
75         private static final String ATTR_AXIS = "axis";
76         private static final String ATTR_POSTSCRIPT_NAME = "name";
77 
78         private final @NonNull String mPostScriptName;
79         private final @NonNull FontStyle mFontStyle;
80         private final @IntRange(from = 0) int mIndex;
81         private final @NonNull String mFontVariationSettings;
82 
Font(@onNull String postScriptName, @NonNull FontStyle fontStyle, @IntRange(from = 0) int index, @NonNull String fontVariationSettings)83         public Font(@NonNull String postScriptName, @NonNull FontStyle fontStyle,
84                 @IntRange(from = 0) int index, @NonNull String fontVariationSettings) {
85             mPostScriptName = postScriptName;
86             mFontStyle = fontStyle;
87             mIndex = index;
88             mFontVariationSettings = fontVariationSettings;
89         }
90 
91         @Override
describeContents()92         public int describeContents() {
93             return 0;
94         }
95 
96         @Override
writeToParcel(Parcel dest, int flags)97         public void writeToParcel(Parcel dest, int flags) {
98             dest.writeString8(mPostScriptName);
99             dest.writeInt(mFontStyle.getWeight());
100             dest.writeInt(mFontStyle.getSlant());
101             dest.writeInt(mIndex);
102             dest.writeString8(mFontVariationSettings);
103         }
104 
105         public static final @NonNull Creator<Font> CREATOR = new Creator<Font>() {
106             @Override
107             public Font createFromParcel(Parcel source) {
108                 String fontName = source.readString8();
109                 int weight = source.readInt();
110                 int slant = source.readInt();
111                 int index = source.readInt();
112                 String varSettings = source.readString8();
113                 return new Font(fontName, new FontStyle(weight, slant), index, varSettings);
114             }
115 
116             @Override
117             public Font[] newArray(int size) {
118                 return new Font[size];
119             }
120         };
121 
122         /**
123          * Write {@link Font} instance to XML file.
124          *
125          * For the XML format, see {@link Font} class comment.
126          *
127          * @param out output XML serializer
128          * @param font a Font instance to be written.
129          */
writeToXml(TypedXmlSerializer out, Font font)130         public static void writeToXml(TypedXmlSerializer out, Font font) throws IOException {
131             out.attribute(null, ATTR_POSTSCRIPT_NAME, font.getPostScriptName());
132             out.attributeInt(null, ATTR_INDEX, font.getIndex());
133             out.attributeInt(null, ATTR_WEIGHT, font.getFontStyle().getWeight());
134             out.attributeInt(null, ATTR_SLANT, font.getFontStyle().getSlant());
135             out.attribute(null, ATTR_AXIS, font.getFontVariationSettings());
136         }
137 
138         /**
139          * Read {@link Font} instance from &lt;font&gt; element in XML
140          *
141          * For the XML format, see {@link Font} class comment.
142          *
143          * @param parser a parser that point &lt;font&gt; element.
144          * @return a font instance
145          * @throws IOException if font element is invalid.
146          */
readFromXml(XmlPullParser parser)147         public static Font readFromXml(XmlPullParser parser) throws IOException {
148             String psName = parser.getAttributeValue(null, ATTR_POSTSCRIPT_NAME);
149             if (psName == null) {
150                 throw new IOException("name attribute is missing in font tag.");
151             }
152             int index = getAttributeValueInt(parser, ATTR_INDEX, 0);
153             int weight = getAttributeValueInt(parser, ATTR_WEIGHT, FontStyle.FONT_WEIGHT_NORMAL);
154             int slant = getAttributeValueInt(parser, ATTR_SLANT, FontStyle.FONT_SLANT_UPRIGHT);
155             String varSettings = parser.getAttributeValue(null, ATTR_AXIS);
156             if (varSettings == null) {
157                 varSettings = "";
158             }
159             return new Font(psName, new FontStyle(weight, slant), index, varSettings);
160         }
161 
getPostScriptName()162         public @NonNull String getPostScriptName() {
163             return mPostScriptName;
164         }
165 
getFontStyle()166         public @NonNull FontStyle getFontStyle() {
167             return mFontStyle;
168         }
169 
getIndex()170         public @IntRange(from = 0) int getIndex() {
171             return mIndex;
172         }
173 
getFontVariationSettings()174         public @NonNull String getFontVariationSettings() {
175             return mFontVariationSettings;
176         }
177 
178         @Override
equals(Object o)179         public boolean equals(Object o) {
180             if (this == o) return true;
181             if (o == null || getClass() != o.getClass()) return false;
182             Font font = (Font) o;
183             return mIndex == font.mIndex
184                     && mPostScriptName.equals(font.mPostScriptName)
185                     && mFontStyle.equals(font.mFontStyle)
186                     && mFontVariationSettings.equals(font.mFontVariationSettings);
187         }
188 
189         @Override
hashCode()190         public int hashCode() {
191             return Objects.hash(mPostScriptName, mFontStyle, mIndex, mFontVariationSettings);
192         }
193 
194         @Override
toString()195         public String toString() {
196             return "Font{"
197                     + "mPostScriptName='" + mPostScriptName + '\''
198                     + ", mFontStyle=" + mFontStyle
199                     + ", mIndex=" + mIndex
200                     + ", mFontVariationSettings='" + mFontVariationSettings + '\''
201                     + '}';
202         }
203     }
204 
205     /**
206      * Font Family object used for update request.
207      */
208     public static final class Family implements Parcelable {
209         private static final String TAG_FAMILY = "family";
210         private static final String ATTR_NAME = "name";
211         private static final String TAG_FONT = "font";
212 
213         private final @NonNull String mName;
214         private final @NonNull List<Font> mFonts;
215 
Family(String name, List<Font> fonts)216         public Family(String name, List<Font> fonts) {
217             mName = name;
218             mFonts = fonts;
219         }
220 
221         @Override
describeContents()222         public int describeContents() {
223             return 0;
224         }
225 
226         @Override
writeToParcel(Parcel dest, int flags)227         public void writeToParcel(Parcel dest, int flags) {
228             dest.writeString8(mName);
229             dest.writeParcelableList(mFonts, flags);
230         }
231 
232         public static final @NonNull Creator<Family> CREATOR = new Creator<Family>() {
233 
234             @Override
235             public Family createFromParcel(Parcel source) {
236                 String familyName = source.readString8();
237                 List<Font> fonts = source.readParcelableList(
238                         new ArrayList<>(), Font.class.getClassLoader());
239                 return new Family(familyName, fonts);
240             }
241 
242             @Override
243             public Family[] newArray(int size) {
244                 return new Family[size];
245             }
246         };
247 
248         /**
249          * Write {@link Family} instance to XML.
250          *
251          * For the XML format, see {@link Font} class comment.
252          *
253          * @param out an output XML serializer
254          * @param family a {@link Family} instance to be written
255          */
writeFamilyToXml(@onNull TypedXmlSerializer out, @NonNull Family family)256         public static void writeFamilyToXml(@NonNull TypedXmlSerializer out, @NonNull Family family)
257                 throws IOException {
258             out.attribute(null, ATTR_NAME, family.getName());
259             List<Font> fonts = family.getFonts();
260             for (int i = 0; i < fonts.size(); ++i) {
261                 Font font = fonts.get(i);
262                 out.startTag(null, TAG_FONT);
263                 Font.writeToXml(out, font);
264                 out.endTag(null, TAG_FONT);
265             }
266         }
267 
268         /**
269          * Read a {@link Family} instance from &lt;family&gt; element in XML
270          *
271          * For the XML format, see {@link Font} class comment.
272          *
273          * @param parser an XML parser that points &lt;family&gt; element.
274          * @return an {@link Family} instance
275          */
readFromXml(@onNull XmlPullParser parser)276         public static @NonNull Family readFromXml(@NonNull XmlPullParser parser)
277                 throws XmlPullParserException, IOException {
278             List<Font> fonts = new ArrayList<>();
279             if (parser.getEventType() != XmlPullParser.START_TAG
280                     || !parser.getName().equals(TAG_FAMILY)) {
281                 throw new IOException("Unexpected parser state: must be START_TAG with family");
282             }
283             String name = parser.getAttributeValue(null, ATTR_NAME);
284             if (name == null) {
285                 throw new IOException("name attribute is missing in family tag.");
286             }
287             int type = 0;
288             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
289                 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_FONT)) {
290                     fonts.add(Font.readFromXml(parser));
291                 } else if (type == XmlPullParser.END_TAG && parser.getName().equals(TAG_FAMILY)) {
292                     break;
293                 }
294             }
295             return new Family(name, fonts);
296         }
297 
getName()298         public @NonNull String getName() {
299             return mName;
300         }
301 
getFonts()302         public @NonNull List<Font> getFonts() {
303             return mFonts;
304         }
305 
306         @Override
equals(Object o)307         public boolean equals(Object o) {
308             if (this == o) return true;
309             if (o == null || getClass() != o.getClass()) return false;
310             Family family = (Family) o;
311             return mName.equals(family.mName) && mFonts.equals(family.mFonts);
312         }
313 
314         @Override
hashCode()315         public int hashCode() {
316             return Objects.hash(mName, mFonts);
317         }
318 
319         @Override
toString()320         public String toString() {
321             return "Family{mName='" + mName + '\'' + ", mFonts=" + mFonts + '}';
322         }
323     }
324 
325     public static final Creator<FontUpdateRequest> CREATOR = new Creator<FontUpdateRequest>() {
326         @Override
327         public FontUpdateRequest createFromParcel(Parcel in) {
328             return new FontUpdateRequest(in);
329         }
330 
331         @Override
332         public FontUpdateRequest[] newArray(int size) {
333             return new FontUpdateRequest[size];
334         }
335     };
336 
337     private final @Type int mType;
338     // NonNull if mType == TYPE_UPDATE_FONT_FILE.
339     @Nullable
340     private final ParcelFileDescriptor mFd;
341     // NonNull if mType == TYPE_UPDATE_FONT_FILE.
342     @Nullable
343     private final byte[] mSignature;
344     // NonNull if mType == TYPE_UPDATE_FONT_FAMILY.
345     @Nullable
346     private final Family mFontFamily;
347 
FontUpdateRequest(@onNull ParcelFileDescriptor fd, @NonNull byte[] signature)348     public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) {
349         mType = TYPE_UPDATE_FONT_FILE;
350         mFd = fd;
351         mSignature = signature;
352         mFontFamily = null;
353     }
354 
FontUpdateRequest(@onNull Family fontFamily)355     public FontUpdateRequest(@NonNull Family fontFamily) {
356         mType = TYPE_UPDATE_FONT_FAMILY;
357         mFd = null;
358         mSignature = null;
359         mFontFamily = fontFamily;
360     }
361 
FontUpdateRequest(@onNull String familyName, @NonNull List<FontFamilyUpdateRequest.Font> variations)362     public FontUpdateRequest(@NonNull String familyName,
363             @NonNull List<FontFamilyUpdateRequest.Font> variations) {
364         this(createFontFamily(familyName, variations));
365     }
366 
createFontFamily(@onNull String familyName, @NonNull List<FontFamilyUpdateRequest.Font> fonts)367     private static Family createFontFamily(@NonNull String familyName,
368             @NonNull List<FontFamilyUpdateRequest.Font> fonts) {
369         List<Font> updateFonts = new ArrayList<>(fonts.size());
370         for (FontFamilyUpdateRequest.Font font : fonts) {
371             updateFonts.add(new Font(
372                     font.getPostScriptName(),
373                     font.getStyle(),
374                     font.getIndex(),
375                     FontVariationAxis.toFontVariationSettings(font.getAxes())));
376         }
377         return new Family(familyName, updateFonts);
378     }
379 
FontUpdateRequest(Parcel in)380     protected FontUpdateRequest(Parcel in) {
381         mType = in.readInt();
382         mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
383         mSignature = in.readBlob();
384         mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader());
385     }
386 
getType()387     public @Type int getType() {
388         return mType;
389     }
390 
391     @Nullable
getFd()392     public ParcelFileDescriptor getFd() {
393         return mFd;
394     }
395 
396     @Nullable
getSignature()397     public byte[] getSignature() {
398         return mSignature;
399     }
400 
401     @Nullable
getFontFamily()402     public Family getFontFamily() {
403         return mFontFamily;
404     }
405 
406     @Override
describeContents()407     public int describeContents() {
408         return mFd != null ? mFd.describeContents() : 0;
409     }
410 
411     @Override
writeToParcel(Parcel dest, int flags)412     public void writeToParcel(Parcel dest, int flags) {
413         dest.writeInt(mType);
414         dest.writeParcelable(mFd, flags);
415         dest.writeBlob(mSignature);
416         dest.writeParcelable(mFontFamily, flags);
417     }
418 
419     // Utility functions
getAttributeValueInt(XmlPullParser parser, String name, int defaultValue)420     private static int getAttributeValueInt(XmlPullParser parser, String name, int defaultValue) {
421         try {
422             String value = parser.getAttributeValue(null, name);
423             if (value == null) {
424                 return defaultValue;
425             }
426             return Integer.parseInt(value);
427         } catch (NumberFormatException e) {
428             return defaultValue;
429         }
430     }
431 }
432