| /* |
| * 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 android.util.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. |
| * <family name="my-sans"> |
| * <font name="MySans" weight="400" slant="0" axis="'wght' 400 'ital' 0" index="0" /> |
| * <font name="MySans" weight="400" slant="0" axis="'wght' 400 'ital' 1" index="0" /> |
| * <font name="MySans" weight="400" slant="0" axis="'wght' 700 'ital' 0" index="0" /> |
| * <font name="MySans" weight="400" slant="0" axis="'wght' 700 'ital' 1" index="0" /> |
| * </family> |
| * |
| * @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<Font> CREATOR = new Creator<Font>() { |
| @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<Font> mFonts; |
| |
| public Family(String name, List<Font> 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<Family> CREATOR = new Creator<Family>() { |
| |
| @Override |
| public Family createFromParcel(Parcel source) { |
| String familyName = source.readString8(); |
| List<Font> fonts = source.readParcelableList( |
| new ArrayList<>(), Font.class.getClassLoader()); |
| 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<Font> 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<Font> 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<Font> 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<FontUpdateRequest> CREATOR = new Creator<FontUpdateRequest>() { |
| @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<FontFamilyUpdateRequest.Font> variations) { |
| this(createFontFamily(familyName, variations)); |
| } |
| |
| private static Family createFontFamily(@NonNull String familyName, |
| @NonNull List<FontFamilyUpdateRequest.Font> fonts) { |
| List<Font> 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()); |
| mSignature = in.readBlob(); |
| mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader()); |
| } |
| |
| 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; |
| } |
| } |
| } |