Merge "Implement .xml font resource support"

This commit is contained in:
TreeHugger Robot
2017-01-30 09:37:10 +00:00
committed by Android (Google) Code Review
11 changed files with 307 additions and 28 deletions

View File

@@ -10864,6 +10864,7 @@ package android.content.res {
method public int getDimensionPixelSize(int, int);
method public android.graphics.drawable.Drawable getDrawable(int);
method public float getFloat(int, float);
method public android.graphics.Typeface getFont(int);
method public float getFraction(int, int, int, float);
method public int getIndex(int);
method public int getIndexCount();

View File

@@ -11426,6 +11426,7 @@ package android.content.res {
method public int getDimensionPixelSize(int, int);
method public android.graphics.drawable.Drawable getDrawable(int);
method public float getFloat(int, float);
method public android.graphics.Typeface getFont(int);
method public float getFraction(int, int, int, float);
method public int getIndex(int);
method public int getIndexCount();

View File

@@ -10898,6 +10898,7 @@ package android.content.res {
method public int getDimensionPixelSize(int, int);
method public android.graphics.drawable.Drawable getDrawable(int);
method public float getFloat(int, float);
method public android.graphics.Typeface getFont(int);
method public float getFraction(int, int, int, float);
method public int getIndex(int);
method public int getIndexCount();

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2017 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
/**
* Denotes that an integer parameter, field or method return value is expected
* to be a Font resource reference (e.g. R.font.myfont).
*
* @hide
*/
@Documented
@Retention(SOURCE)
@Target({METHOD, PARAMETER, FIELD})
public @interface FontRes {
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2017 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.content.res;
import com.android.internal.R;
import android.text.FontConfig;
import android.util.AttributeSet;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Parser for xml type font resources.
* @hide
*/
public class FontResourcesParser {
private static final int NORMAL_WEIGHT = 400;
private static final String ITALIC = "italic";
public static FontConfig parse(XmlPullParser parser, Resources resources)
throws XmlPullParserException, IOException {
int type;
while ((type=parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Empty loop.
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
return readFamilies(parser, resources);
}
private static FontConfig readFamilies(XmlPullParser parser, Resources resources)
throws XmlPullParserException, IOException {
FontConfig config = new FontConfig();
parser.require(XmlPullParser.START_TAG, null, "font-family");
String tag = parser.getName();
if (tag.equals("font-family")) {
config.getFamilies().add(readFamily(parser, resources));
} else {
skip(parser);
}
return config;
}
private static FontConfig.Family readFamily(XmlPullParser parser, Resources resources)
throws XmlPullParserException, IOException {
List<FontConfig.Font> fonts = new ArrayList<>();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("font")) {
fonts.add(readFont(parser, resources));
} else {
skip(parser);
}
}
return new FontConfig.Family(null, fonts, null, null);
}
private static FontConfig.Font readFont(XmlPullParser parser, Resources resources)
throws XmlPullParserException, IOException {
AttributeSet attrs = Xml.asAttributeSet(parser);
TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont);
int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight, NORMAL_WEIGHT);
boolean isItalic = ITALIC.equals(array.getString(R.styleable.FontFamilyFont_fontStyle));
String filename = array.getString(R.styleable.FontFamilyFont_font);
array.recycle();
return new FontConfig.Font(filename, 0, null, weight, isItalic);
}
private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
int depth = 1;
while (depth > 0) {
switch (parser.next()) {
case XmlPullParser.START_TAG:
depth++;
break;
case XmlPullParser.END_TAG:
depth--;
break;
}
}
}
}

View File

@@ -27,6 +27,7 @@ import android.annotation.ColorInt;
import android.annotation.ColorRes;
import android.annotation.DimenRes;
import android.annotation.DrawableRes;
import android.annotation.FontRes;
import android.annotation.FractionRes;
import android.annotation.IntegerRes;
import android.annotation.LayoutRes;
@@ -350,12 +351,12 @@ public class Resources {
*
* @return Typeface The Typeface data associated with the resource.
*/
@NonNull public Typeface getFont(@StringRes int id) throws NotFoundException {
@NonNull public Typeface getFont(@FontRes int id) throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
Typeface typeface = impl.loadFont(value, id);
Typeface typeface = impl.loadFont(this, value, id);
if (typeface != null) {
return typeface;
}
@@ -366,6 +367,11 @@ public class Resources {
+ Integer.toHexString(id));
}
@NonNull
Typeface getFont(@NonNull TypedValue value, @FontRes int id) throws NotFoundException {
return mResourcesImpl.loadFont(this, value, id);
}
/**
* Returns the character sequence necessary for grammatically correct pluralization
* of the given resource ID for the given quantity.

View File

@@ -39,6 +39,7 @@ import android.icu.text.PluralRules;
import android.os.Build;
import android.os.LocaleList;
import android.os.Trace;
import android.text.FontConfig;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -46,7 +47,6 @@ import android.util.LongSparseArray;
import android.util.Slog;
import android.util.TypedValue;
import android.util.Xml;
import android.view.Display;
import android.view.DisplayAdjustments;
import java.io.IOException;
@@ -746,13 +746,17 @@ public class ResourcesImpl {
* Loads a font from XML or resources stream.
*/
@Nullable
public Typeface loadFont(TypedValue value, int id) {
public Typeface loadFont(Resources wrapper, TypedValue value, int id) {
if (value.string == null) {
throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+ Integer.toHexString(id) + ") is not a Font: " + value);
}
final String file = value.string.toString();
Typeface cached = Typeface.createFromCache(mAssets, file);
if (cached != null) {
return cached;
}
if (DEBUG_LOAD) {
Log.v(TAG, "Loading font for cookie " + value.assetCookie + ": " + file);
@@ -760,12 +764,17 @@ public class ResourcesImpl {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
if (file.endsWith(".xml")) {
// TODO handle xml type font definitions
} else {
return Typeface.createFromResources(
mAssets, value.string.toString(), value.assetCookie);
if (file.endsWith("xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "font");
final FontConfig config = FontResourcesParser.parse(rp, wrapper);
return Typeface.createFromResources(config, mAssets, file);
}
return Typeface.createFromResources(mAssets, file, value.assetCookie);
} catch (XmlPullParserException e) {
Log.e(TAG, "Failed to parse xml resource " + file, e);
} catch (IOException e) {
Log.e(TAG, "Failed to read xml resource " + file, e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}

View File

@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.annotation.StyleableRes;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.StrictMode;
import android.util.AttributeSet;
@@ -936,6 +937,36 @@ public class TypedArray {
return null;
}
/**
* Retrieve the Typeface for the attribute at <var>index</var>.
* <p>
* This method will throw an exception if the attribute is defined but is
* not a font.
*
* @param index Index of attribute to retrieve.
*
* @return Typeface for the attribute, or {@code null} if not defined.
* @throws RuntimeException if the TypedArray has already been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not a font resource.
*/
@Nullable
public Typeface getFont(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
return mResources.getFont(value, value.resourceId);
}
return null;
}
/**
* Retrieve the CharSequence[] for the attribute at <var>index</var>.
* This gets the resource ID of the selected attribute, and uses

View File

@@ -164,7 +164,7 @@ public final class FontConfig implements Parcelable {
* Class that holds information about a Font.
*/
public static final class Font implements Parcelable {
private final String mFontName;
private String mFontName;
private final int mTtcIndex;
private final List<Axis> mAxes;
private final int mWeight;
@@ -202,6 +202,13 @@ public final class FontConfig implements Parcelable {
return mFontName;
}
/**
* @hide
*/
public void setFontName(String fontName) {
mFontName = fontName;
}
/**
* Returns the index to be used to access this font when accessing a TTC file.
*/

View File

@@ -787,6 +787,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ColorStateList textColorLink = null;
int textSize = 15;
String fontFamily = null;
Typeface fontTypeface = null;
boolean fontFamilyExplicit = false;
int typefaceIndex = -1;
int styleIndex = -1;
@@ -849,7 +850,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextAppearance_fontFamily:
fontFamily = appearance.getString(attr);
try {
fontTypeface = appearance.getFont(attr);
} catch (UnsupportedOperationException e) {
// Expected if it is not a font resource.
}
if (fontTypeface == null) {
fontFamily = appearance.getString(attr);
}
break;
case com.android.internal.R.styleable.TextAppearance_textStyle:
@@ -1153,7 +1161,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_fontFamily:
fontFamily = a.getString(attr);
try {
fontTypeface = appearance.getFont(attr);
} catch (UnsupportedOperationException e) {
// Expected if it is not a font resource.
}
if (fontTypeface == null) {
fontFamily = appearance.getString(attr);
}
fontFamilyExplicit = true;
break;
@@ -1503,7 +1518,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (typefaceIndex != -1 && !fontFamilyExplicit) {
fontFamily = null;
}
setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
if (shadowcolor != 0) {
setShadowLayer(r, dx, dy, shadowcolor);
@@ -1796,14 +1811,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
Typeface tf = null;
if (familyName != null) {
private void setTypefaceFromAttrs(Typeface fontTypeface, String familyName, int typefaceIndex,
int styleIndex) {
Typeface tf = fontTypeface;
if (tf == null && familyName != null) {
tf = Typeface.create(familyName, styleIndex);
if (tf != null) {
setTypeface(tf);
return;
}
}
if (tf != null) {
setTypeface(tf);
return;
}
switch (typefaceIndex) {
case SANS:
@@ -3101,10 +3117,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setLinkTextColor(textColorLink);
}
final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
Typeface fontTypeface = null;
String fontFamily = null;
try {
fontTypeface = ta.getFont(R.styleable.TextAppearance_fontFamily);
} catch (UnsupportedOperationException e) {
// Expected if it is not a font resource.
}
if (fontTypeface == null) {
fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
}
final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
if (shadowColor != 0) {
@@ -5165,15 +5190,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean forceUpdate = false;
if (isPassword) {
setTransformationMethod(PasswordTransformationMethod.getInstance());
setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
} else if (isVisiblePassword) {
if (mTransformation == PasswordTransformationMethod.getInstance()) {
forceUpdate = true;
}
setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
} else if (wasPassword || wasVisiblePassword) {
// not in password mode, clean up typeface and transformation
setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, -1, -1);
if (mTransformation == PasswordTransformationMethod.getInstance()) {
forceUpdate = true;
}

View File

@@ -18,6 +18,7 @@ package android.graphics;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.AssetManager;
import android.graphics.fonts.FontRequest;
import android.graphics.fonts.FontResult;
@@ -131,9 +132,9 @@ public class Typeface {
/**
* @hide
* Used by Resources.
* Used by Resources to load a font resource of type font file.
*/
@NonNull
@Nullable
public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
if (sFallbackFonts != null) {
synchronized (sDynamicTypefaceCache) {
@@ -143,6 +144,7 @@ public class Typeface {
FontFamily fontFamily = new FontFamily();
if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */)) {
fontFamily.freeze();
FontFamily[] families = {fontFamily};
typeface = createFromFamiliesWithDefault(families);
sDynamicTypefaceCache.put(key, typeface);
@@ -150,7 +152,62 @@ public class Typeface {
}
}
}
throw new RuntimeException("Font resource not found " + path);
return null;
}
/**
* @hide
* Used by Resources to load a font resource of type xml.
*/
@Nullable
public static Typeface createFromResources(FontConfig config, AssetManager mgr, String path) {
if (sFallbackFonts != null) {
synchronized (sDynamicTypefaceCache) {
final String key = createAssetUid(mgr, path);
Typeface typeface = sDynamicTypefaceCache.get(key);
if (typeface != null) return typeface;
List<FontConfig.Family> families = config.getFamilies();
if (families == null || families.isEmpty()) {
throw new RuntimeException("Font resource contained no fonts.");
}
if (families.size() > 1) {
throw new RuntimeException("Font resource contained more than one family.");
}
FontConfig.Family family = families.get(0);
FontFamily fontFamily = new FontFamily();
List<FontConfig.Font> fonts = family.getFonts();
for (int i = 0; i < fonts.size(); i++) {
FontConfig.Font font = fonts.get(i);
// TODO: Use style and weight info
if (!fontFamily.addFontFromAssetManager(mgr, font.getFontName(),
0 /* resourceCookie */, false /* isAsset */)) {
return null;
}
}
fontFamily.freeze();
FontFamily[] familyChain = { fontFamily };
typeface = createFromFamiliesWithDefault(familyChain);
sDynamicTypefaceCache.put(key, typeface);
return typeface;
}
}
return null;
}
/**
* @hide
*/
public static Typeface createFromCache(AssetManager mgr, String path) {
synchronized (sDynamicTypefaceCache) {
final String key = createAssetUid(mgr, path);
Typeface typeface = sDynamicTypefaceCache.get(key);
if (typeface != null) {
return typeface;
}
}
return null;
}
/**