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