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 <font> element in XML 140 * 141 * For the XML format, see {@link Font} class comment. 142 * 143 * @param parser a parser that point <font> 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 <family> element in XML 270 * 271 * For the XML format, see {@link Font} class comment. 272 * 273 * @param parser an XML parser that points <family> 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