1 /* 2 * Copyright (C) 2018 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 static android.text.FontConfig.Alias; 20 import static android.text.FontConfig.NamedFamilyList; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.graphics.FontListParser; 25 import android.util.Xml; 26 27 import org.xmlpull.v1.XmlPullParser; 28 import org.xmlpull.v1.XmlPullParserException; 29 30 import java.io.File; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 39 /** 40 * Parser for font customization 41 * 42 * @hide 43 */ 44 public class FontCustomizationParser { 45 private static final String TAG = "FontCustomizationParser"; 46 47 /** 48 * Represents a customization XML 49 */ 50 public static class Result { 51 private final Map<String, NamedFamilyList> mAdditionalNamedFamilies; 52 53 private final List<Alias> mAdditionalAliases; 54 Result()55 public Result() { 56 mAdditionalNamedFamilies = Collections.emptyMap(); 57 mAdditionalAliases = Collections.emptyList(); 58 } 59 Result(Map<String, NamedFamilyList> additionalNamedFamilies, List<Alias> additionalAliases)60 public Result(Map<String, NamedFamilyList> additionalNamedFamilies, 61 List<Alias> additionalAliases) { 62 mAdditionalNamedFamilies = additionalNamedFamilies; 63 mAdditionalAliases = additionalAliases; 64 } 65 getAdditionalNamedFamilies()66 public Map<String, NamedFamilyList> getAdditionalNamedFamilies() { 67 return mAdditionalNamedFamilies; 68 } 69 getAdditionalAliases()70 public List<Alias> getAdditionalAliases() { 71 return mAdditionalAliases; 72 } 73 } 74 75 /** 76 * Parses the customization XML 77 * 78 * Caller must close the input stream 79 */ parse( @onNull InputStream in, @NonNull String fontDir, @Nullable Map<String, File> updatableFontMap )80 public static Result parse( 81 @NonNull InputStream in, 82 @NonNull String fontDir, 83 @Nullable Map<String, File> updatableFontMap 84 ) throws XmlPullParserException, IOException { 85 XmlPullParser parser = Xml.newPullParser(); 86 parser.setInput(in, null); 87 parser.nextTag(); 88 return readFamilies(parser, fontDir, updatableFontMap); 89 } 90 validateAndTransformToResult( List<NamedFamilyList> families, List<Alias> aliases)91 private static Result validateAndTransformToResult( 92 List<NamedFamilyList> families, List<Alias> aliases) { 93 HashMap<String, NamedFamilyList> namedFamily = new HashMap<>(); 94 for (int i = 0; i < families.size(); ++i) { 95 final NamedFamilyList family = families.get(i); 96 final String name = family.getName(); 97 if (name != null) { 98 if (namedFamily.put(name, family) != null) { 99 throw new IllegalArgumentException( 100 "new-named-family requires unique name attribute"); 101 } 102 } else { 103 throw new IllegalArgumentException( 104 "new-named-family requires name attribute or new-default-fallback-family" 105 + "requires fallackTarget attribute"); 106 } 107 } 108 return new Result(namedFamily, aliases); 109 } 110 readFamilies( @onNull XmlPullParser parser, @NonNull String fontDir, @Nullable Map<String, File> updatableFontMap )111 private static Result readFamilies( 112 @NonNull XmlPullParser parser, 113 @NonNull String fontDir, 114 @Nullable Map<String, File> updatableFontMap 115 ) throws XmlPullParserException, IOException { 116 List<NamedFamilyList> families = new ArrayList<>(); 117 List<Alias> aliases = new ArrayList<>(); 118 parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); 119 while (parser.next() != XmlPullParser.END_TAG) { 120 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 121 String tag = parser.getName(); 122 if (tag.equals("family")) { 123 readFamily(parser, fontDir, families, updatableFontMap); 124 } else if (tag.equals("family-list")) { 125 readFamilyList(parser, fontDir, families, updatableFontMap); 126 } else if (tag.equals("alias")) { 127 aliases.add(FontListParser.readAlias(parser)); 128 } else { 129 FontListParser.skip(parser); 130 } 131 } 132 return validateAndTransformToResult(families, aliases); 133 } 134 readFamily( @onNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, @Nullable Map<String, File> updatableFontMap)135 private static void readFamily( 136 @NonNull XmlPullParser parser, 137 @NonNull String fontDir, 138 @NonNull List<NamedFamilyList> out, 139 @Nullable Map<String, File> updatableFontMap) 140 throws XmlPullParserException, IOException { 141 final String customizationType = parser.getAttributeValue(null, "customizationType"); 142 if (customizationType == null) { 143 throw new IllegalArgumentException("customizationType must be specified"); 144 } 145 if (customizationType.equals("new-named-family")) { 146 NamedFamilyList fontFamily = FontListParser.readNamedFamily( 147 parser, fontDir, updatableFontMap, false); 148 if (fontFamily != null) { 149 out.add(fontFamily); 150 } 151 } else { 152 throw new IllegalArgumentException("Unknown customizationType=" + customizationType); 153 } 154 } 155 readFamilyList( @onNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, @Nullable Map<String, File> updatableFontMap)156 private static void readFamilyList( 157 @NonNull XmlPullParser parser, 158 @NonNull String fontDir, 159 @NonNull List<NamedFamilyList> out, 160 @Nullable Map<String, File> updatableFontMap) 161 throws XmlPullParserException, IOException { 162 final String customizationType = parser.getAttributeValue(null, "customizationType"); 163 if (customizationType == null) { 164 throw new IllegalArgumentException("customizationType must be specified"); 165 } 166 if (customizationType.equals("new-named-family")) { 167 NamedFamilyList fontFamily = FontListParser.readNamedFamilyList( 168 parser, fontDir, updatableFontMap, false); 169 if (fontFamily != null) { 170 out.add(fontFamily); 171 } 172 } else { 173 throw new IllegalArgumentException("Unknown customizationType=" + customizationType); 174 } 175 } 176 } 177