/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.graphics.fonts; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.text.FontConfig; import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * Represents a font update request. Currently only font install request is supported. * @hide */ public final class FontUpdateRequest implements Parcelable { public static final int TYPE_UPDATE_FONT_FILE = 0; public static final int TYPE_UPDATE_FONT_FAMILY = 1; @IntDef(prefix = "TYPE_", value = { TYPE_UPDATE_FONT_FILE, TYPE_UPDATE_FONT_FAMILY, }) @Retention(RetentionPolicy.SOURCE) public @interface Type {} /** * Font object used for update. * * Here is an example of Family/Font XML. * * * * * * * * @see Font#readFromXml(XmlPullParser) * @see Font#writeToXml(TypedXmlSerializer, Font) * @see Family#readFromXml(XmlPullParser) * @see Family#writeFamilyToXml(TypedXmlSerializer, Family) */ public static final class Font implements Parcelable { private static final String ATTR_INDEX = "index"; private static final String ATTR_WEIGHT = "weight"; private static final String ATTR_SLANT = "slant"; private static final String ATTR_AXIS = "axis"; private static final String ATTR_POSTSCRIPT_NAME = "name"; private final @NonNull String mPostScriptName; private final @NonNull FontStyle mFontStyle; private final @IntRange(from = 0) int mIndex; private final @NonNull String mFontVariationSettings; public Font(@NonNull String postScriptName, @NonNull FontStyle fontStyle, @IntRange(from = 0) int index, @NonNull String fontVariationSettings) { mPostScriptName = postScriptName; mFontStyle = fontStyle; mIndex = index; mFontVariationSettings = fontVariationSettings; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString8(mPostScriptName); dest.writeInt(mFontStyle.getWeight()); dest.writeInt(mFontStyle.getSlant()); dest.writeInt(mIndex); dest.writeString8(mFontVariationSettings); } public static final @NonNull Creator CREATOR = new Creator() { @Override public Font createFromParcel(Parcel source) { String fontName = source.readString8(); int weight = source.readInt(); int slant = source.readInt(); int index = source.readInt(); String varSettings = source.readString8(); return new Font(fontName, new FontStyle(weight, slant), index, varSettings); } @Override public Font[] newArray(int size) { return new Font[size]; } }; /** * Write {@link Font} instance to XML file. * * For the XML format, see {@link Font} class comment. * * @param out output XML serializer * @param font a Font instance to be written. */ public static void writeToXml(TypedXmlSerializer out, Font font) throws IOException { out.attribute(null, ATTR_POSTSCRIPT_NAME, font.getPostScriptName()); out.attributeInt(null, ATTR_INDEX, font.getIndex()); out.attributeInt(null, ATTR_WEIGHT, font.getFontStyle().getWeight()); out.attributeInt(null, ATTR_SLANT, font.getFontStyle().getSlant()); out.attribute(null, ATTR_AXIS, font.getFontVariationSettings()); } /** * Read {@link Font} instance from <font> element in XML * * For the XML format, see {@link Font} class comment. * * @param parser a parser that point <font> element. * @return a font instance * @throws IOException if font element is invalid. */ public static Font readFromXml(XmlPullParser parser) throws IOException { String psName = parser.getAttributeValue(null, ATTR_POSTSCRIPT_NAME); if (psName == null) { throw new IOException("name attribute is missing in font tag."); } int index = getAttributeValueInt(parser, ATTR_INDEX, 0); int weight = getAttributeValueInt(parser, ATTR_WEIGHT, FontStyle.FONT_WEIGHT_NORMAL); int slant = getAttributeValueInt(parser, ATTR_SLANT, FontStyle.FONT_SLANT_UPRIGHT); String varSettings = parser.getAttributeValue(null, ATTR_AXIS); if (varSettings == null) { varSettings = ""; } return new Font(psName, new FontStyle(weight, slant), index, varSettings); } public @NonNull String getPostScriptName() { return mPostScriptName; } public @NonNull FontStyle getFontStyle() { return mFontStyle; } public @IntRange(from = 0) int getIndex() { return mIndex; } public @NonNull String getFontVariationSettings() { return mFontVariationSettings; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Font font = (Font) o; return mIndex == font.mIndex && mPostScriptName.equals(font.mPostScriptName) && mFontStyle.equals(font.mFontStyle) && mFontVariationSettings.equals(font.mFontVariationSettings); } @Override public int hashCode() { return Objects.hash(mPostScriptName, mFontStyle, mIndex, mFontVariationSettings); } @Override public String toString() { return "Font{" + "mPostScriptName='" + mPostScriptName + '\'' + ", mFontStyle=" + mFontStyle + ", mIndex=" + mIndex + ", mFontVariationSettings='" + mFontVariationSettings + '\'' + '}'; } } /** * Font Family object used for update request. */ public static final class Family implements Parcelable { private static final String TAG_FAMILY = "family"; private static final String ATTR_NAME = "name"; private static final String TAG_FONT = "font"; private final @NonNull String mName; private final @NonNull List mFonts; public Family(String name, List fonts) { mName = name; mFonts = fonts; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString8(mName); dest.writeParcelableList(mFonts, flags); } public static final @NonNull Creator CREATOR = new Creator() { @Override public Family createFromParcel(Parcel source) { String familyName = source.readString8(); List fonts = source.readParcelableList( new ArrayList<>(), Font.class.getClassLoader(), android.graphics.fonts.FontUpdateRequest.Font.class); return new Family(familyName, fonts); } @Override public Family[] newArray(int size) { return new Family[size]; } }; /** * Write {@link Family} instance to XML. * * For the XML format, see {@link Font} class comment. * * @param out an output XML serializer * @param family a {@link Family} instance to be written */ public static void writeFamilyToXml(@NonNull TypedXmlSerializer out, @NonNull Family family) throws IOException { out.attribute(null, ATTR_NAME, family.getName()); List fonts = family.getFonts(); for (int i = 0; i < fonts.size(); ++i) { Font font = fonts.get(i); out.startTag(null, TAG_FONT); Font.writeToXml(out, font); out.endTag(null, TAG_FONT); } } /** * Read a {@link Family} instance from <family> element in XML * * For the XML format, see {@link Font} class comment. * * @param parser an XML parser that points <family> element. * @return an {@link Family} instance */ public static @NonNull Family readFromXml(@NonNull XmlPullParser parser) throws XmlPullParserException, IOException { List fonts = new ArrayList<>(); if (parser.getEventType() != XmlPullParser.START_TAG || !parser.getName().equals(TAG_FAMILY)) { throw new IOException("Unexpected parser state: must be START_TAG with family"); } String name = parser.getAttributeValue(null, ATTR_NAME); if (name == null) { throw new IOException("name attribute is missing in family tag."); } int type = 0; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_FONT)) { fonts.add(Font.readFromXml(parser)); } else if (type == XmlPullParser.END_TAG && parser.getName().equals(TAG_FAMILY)) { break; } } return new Family(name, fonts); } public @NonNull String getName() { return mName; } public @NonNull List getFonts() { return mFonts; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Family family = (Family) o; return mName.equals(family.mName) && mFonts.equals(family.mFonts); } @Override public int hashCode() { return Objects.hash(mName, mFonts); } @Override public String toString() { return "Family{mName='" + mName + '\'' + ", mFonts=" + mFonts + '}'; } } public static final Creator CREATOR = new Creator() { @Override public FontUpdateRequest createFromParcel(Parcel in) { return new FontUpdateRequest(in); } @Override public FontUpdateRequest[] newArray(int size) { return new FontUpdateRequest[size]; } }; private final @Type int mType; // NonNull if mType == TYPE_UPDATE_FONT_FILE. @Nullable private final ParcelFileDescriptor mFd; // NonNull if mType == TYPE_UPDATE_FONT_FILE. @Nullable private final byte[] mSignature; // NonNull if mType == TYPE_UPDATE_FONT_FAMILY. @Nullable private final Family mFontFamily; public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) { mType = TYPE_UPDATE_FONT_FILE; mFd = fd; mSignature = signature; mFontFamily = null; } public FontUpdateRequest(@NonNull Family fontFamily) { mType = TYPE_UPDATE_FONT_FAMILY; mFd = null; mSignature = null; mFontFamily = fontFamily; } public FontUpdateRequest(@NonNull String familyName, @NonNull List variations) { this(createFontFamily(familyName, variations)); } private static Family createFontFamily(@NonNull String familyName, @NonNull List fonts) { List updateFonts = new ArrayList<>(fonts.size()); for (FontFamilyUpdateRequest.Font font : fonts) { updateFonts.add(new Font( font.getPostScriptName(), font.getStyle(), font.getIndex(), FontVariationAxis.toFontVariationSettings(font.getAxes()))); } return new Family(familyName, updateFonts); } protected FontUpdateRequest(Parcel in) { mType = in.readInt(); mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class); mSignature = in.readBlob(); mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader(), android.graphics.fonts.FontUpdateRequest.Family.class); } public @Type int getType() { return mType; } @Nullable public ParcelFileDescriptor getFd() { return mFd; } @Nullable public byte[] getSignature() { return mSignature; } @Nullable public Family getFontFamily() { return mFontFamily; } @Override public int describeContents() { return mFd != null ? mFd.describeContents() : 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); dest.writeParcelable(mFd, flags); dest.writeBlob(mSignature); dest.writeParcelable(mFontFamily, flags); } // Utility functions private static int getAttributeValueInt(XmlPullParser parser, String name, int defaultValue) { try { String value = parser.getAttributeValue(null, name); if (value == null) { return defaultValue; } return Integer.parseInt(value); } catch (NumberFormatException e) { return defaultValue; } } }