1/* 2 * Copyright (C) 2017 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 */ 16package android.content.res; 17 18import com.android.internal.R; 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.graphics.Typeface; 22import android.util.AttributeSet; 23import android.util.Log; 24import android.util.Xml; 25 26import org.xmlpull.v1.XmlPullParser; 27import org.xmlpull.v1.XmlPullParserException; 28 29import java.io.IOException; 30import java.util.ArrayList; 31import java.util.Arrays; 32import java.util.List; 33 34/** 35 * Parser for xml type font resources. 36 * @hide 37 */ 38public class FontResourcesParser { 39 private static final String TAG = "FontResourcesParser"; 40 41 // A class represents single entry of font-family in xml file. 42 public interface FamilyResourceEntry {} 43 44 // A class represents font provider based font-family element in xml file. 45 public static final class ProviderResourceEntry implements FamilyResourceEntry { 46 private final @NonNull String mProviderAuthority; 47 private final @NonNull String mProviderPackage; 48 private final @NonNull String mQuery; 49 private final @Nullable List<List<String>> mCerts; 50 51 public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg, 52 @NonNull String query, @Nullable List<List<String>> certs) { 53 mProviderAuthority = authority; 54 mProviderPackage = pkg; 55 mQuery = query; 56 mCerts = certs; 57 } 58 59 public @NonNull String getAuthority() { 60 return mProviderAuthority; 61 } 62 63 public @NonNull String getPackage() { 64 return mProviderPackage; 65 } 66 67 public @NonNull String getQuery() { 68 return mQuery; 69 } 70 71 public @Nullable List<List<String>> getCerts() { 72 return mCerts; 73 } 74 } 75 76 // A class represents font element in xml file which points a file in resource. 77 public static final class FontFileResourceEntry { 78 private final @NonNull String mFileName; 79 private int mWeight; 80 private int mItalic; 81 private int mResourceId; 82 83 public FontFileResourceEntry(@NonNull String fileName, int weight, int italic) { 84 mFileName = fileName; 85 mWeight = weight; 86 mItalic = italic; 87 } 88 89 public @NonNull String getFileName() { 90 return mFileName; 91 } 92 93 public int getWeight() { 94 return mWeight; 95 } 96 97 public int getItalic() { 98 return mItalic; 99 } 100 } 101 102 // A class represents file based font-family element in xml file. 103 public static final class FontFamilyFilesResourceEntry implements FamilyResourceEntry { 104 private final @NonNull FontFileResourceEntry[] mEntries; 105 106 public FontFamilyFilesResourceEntry(@NonNull FontFileResourceEntry[] entries) { 107 mEntries = entries; 108 } 109 110 public @NonNull FontFileResourceEntry[] getEntries() { 111 return mEntries; 112 } 113 } 114 115 public static @Nullable FamilyResourceEntry parse(XmlPullParser parser, Resources resources) 116 throws XmlPullParserException, IOException { 117 int type; 118 while ((type=parser.next()) != XmlPullParser.START_TAG 119 && type != XmlPullParser.END_DOCUMENT) { 120 // Empty loop. 121 } 122 123 if (type != XmlPullParser.START_TAG) { 124 throw new XmlPullParserException("No start tag found"); 125 } 126 return readFamilies(parser, resources); 127 } 128 129 private static @Nullable FamilyResourceEntry readFamilies(XmlPullParser parser, 130 Resources resources) throws XmlPullParserException, IOException { 131 parser.require(XmlPullParser.START_TAG, null, "font-family"); 132 String tag = parser.getName(); 133 FamilyResourceEntry result = null; 134 if (tag.equals("font-family")) { 135 return readFamily(parser, resources); 136 } else { 137 skip(parser); 138 Log.e(TAG, "Failed to find font-family tag"); 139 return null; 140 } 141 } 142 143 private static @Nullable FamilyResourceEntry readFamily(XmlPullParser parser, 144 Resources resources) throws XmlPullParserException, IOException { 145 AttributeSet attrs = Xml.asAttributeSet(parser); 146 TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily); 147 String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority); 148 String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage); 149 String query = array.getString(R.styleable.FontFamily_fontProviderQuery); 150 int certsId = array.getResourceId(R.styleable.FontFamily_fontProviderCerts, 0); 151 array.recycle(); 152 if (authority != null && providerPackage != null && query != null) { 153 while (parser.next() != XmlPullParser.END_TAG) { 154 skip(parser); 155 } 156 List<List<String>> certs = null; 157 if (certsId != 0) { 158 TypedArray typedArray = resources.obtainTypedArray(certsId); 159 if (typedArray.length() > 0) { 160 certs = new ArrayList<>(); 161 boolean isArrayOfArrays = typedArray.getResourceId(0, 0) != 0; 162 if (isArrayOfArrays) { 163 for (int i = 0; i < typedArray.length(); i++) { 164 int certId = typedArray.getResourceId(i, 0); 165 String[] certsArray = resources.getStringArray(certId); 166 List<String> certsList = Arrays.asList(certsArray); 167 certs.add(certsList); 168 } 169 } else { 170 String[] certsArray = resources.getStringArray(certsId); 171 List<String> certsList = Arrays.asList(certsArray); 172 certs.add(certsList); 173 } 174 } 175 } 176 return new ProviderResourceEntry(authority, providerPackage, query, certs); 177 } 178 List<FontFileResourceEntry> fonts = new ArrayList<>(); 179 while (parser.next() != XmlPullParser.END_TAG) { 180 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 181 String tag = parser.getName(); 182 if (tag.equals("font")) { 183 final FontFileResourceEntry entry = readFont(parser, resources); 184 if (entry != null) { 185 fonts.add(entry); 186 } 187 } else { 188 skip(parser); 189 } 190 } 191 if (fonts.isEmpty()) { 192 return null; 193 } 194 return new FontFamilyFilesResourceEntry(fonts.toArray( 195 new FontFileResourceEntry[fonts.size()])); 196 } 197 198 private static FontFileResourceEntry readFont(XmlPullParser parser, Resources resources) 199 throws XmlPullParserException, IOException { 200 AttributeSet attrs = Xml.asAttributeSet(parser); 201 TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont); 202 int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight, 203 Typeface.RESOLVE_BY_FONT_TABLE); 204 int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle, 205 Typeface.RESOLVE_BY_FONT_TABLE); 206 String filename = array.getString(R.styleable.FontFamilyFont_font); 207 array.recycle(); 208 while (parser.next() != XmlPullParser.END_TAG) { 209 skip(parser); 210 } 211 if (filename == null) { 212 return null; 213 } 214 return new FontFileResourceEntry(filename, weight, italic); 215 } 216 217 private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { 218 int depth = 1; 219 while (depth > 0) { 220 switch (parser.next()) { 221 case XmlPullParser.START_TAG: 222 depth++; 223 break; 224 case XmlPullParser.END_TAG: 225 depth--; 226 break; 227 } 228 } 229 } 230} 231