FontVariationAxis.java revision 042741ce4f80f6a19c15252e447e66a0f3a891ec
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 */
16
17package android.graphics.fonts;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.text.TextUtils;
22
23import java.util.ArrayList;
24import java.util.regex.Pattern;
25
26/**
27 * Class that holds information about single font variation axis.
28 */
29public final class FontVariationAxis {
30    private final int mTag;
31    private final String mTagString;
32    private final float mStyleValue;
33
34    /**
35     * Construct FontVariationAxis.
36     *
37     * The axis tag must contain four ASCII characters. Tag string that are longer or shorter than
38     * four characters, or contains characters outside of U+0020..U+007E are invalid.
39     *
40     * @throws {@link InvalidFormatException} If given tag string is invalid.
41     */
42    public FontVariationAxis(@NonNull String tagString, float styleValue)
43            throws InvalidFormatException {
44        if (!isValidTag(tagString)) {
45            throw new InvalidFormatException("Invalid tag pattern: " + tagString);
46        }
47        mTag = makeTag(tagString);
48        mTagString = tagString;
49        mStyleValue = styleValue;
50    }
51
52    /**
53     * Returns the OpenType style tag value.
54     * @hide
55     */
56    public int getOpenTypeTagValue() {
57        return mTag;
58    }
59
60    /**
61     * Returns the variable font axis tag associated to this axis.
62     */
63    public String getTag() {
64        return mTagString;
65    }
66
67    /**
68     * Returns the style value associated to the given axis for this font.
69     */
70    public float getStyleValue() {
71        return mStyleValue;
72    }
73
74    /**
75     * Returns a valid font variation setting string for this object.
76     */
77    @Override
78    public @NonNull String toString() {
79        return "'" + mTagString + "' " + Float.toString(mStyleValue);
80    }
81
82    /**
83     * The 'tag' attribute value is read as four character values between U+0020 and U+007E
84     * inclusive.
85     */
86    private static final Pattern TAG_PATTERN = Pattern.compile("[\u0020-\u007E]{4}");
87
88    /**
89     * Returns true if 'tagString' is valid for font variation axis tag.
90     */
91    private static boolean isValidTag(String tagString) {
92        return tagString != null && TAG_PATTERN.matcher(tagString).matches();
93    }
94
95    /**
96     * The 'styleValue' attribute has an optional leading '-', followed by '<digits>',
97     * '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9].
98     */
99    private static final Pattern STYLE_VALUE_PATTERN =
100            Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))");
101
102    private static boolean isValidValueFormat(String valueString) {
103        return valueString != null && STYLE_VALUE_PATTERN.matcher(valueString).matches();
104    }
105
106    /** @hide */
107    public static int makeTag(String tagString) {
108        final char c1 = tagString.charAt(0);
109        final char c2 = tagString.charAt(1);
110        final char c3 = tagString.charAt(2);
111        final char c4 = tagString.charAt(3);
112        return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4;
113    }
114
115    /**
116     * An exception indicates that the format of font variation settings is invalid.
117     */
118    public static class InvalidFormatException extends Exception {
119        public InvalidFormatException(String message) {
120            super(message);
121        }
122    };
123
124    /**
125     * Construct FontVariationAxis array from font variation settings.
126     *
127     * The settings string is constructed from multiple pairs of axis tag and style values. The axis
128     * tag must contain four ASCII characters and must be wrapped with single quotes (U+0027) or
129     * double quotes (U+0022). Axis strings that are longer or shorter than four characters, or
130     * contain characters outside of U+0020..U+007E are invalid. If a specified axis name is not
131     * defined in the font, the settings will be ignored.
132     *
133     * <pre>
134     *   FontVariationAxis.fromFontVariationSettings("'wdth' 1.0");
135     *   FontVariationAxis.fromFontVariationSettings("'AX  ' 1.0, 'FB  ' 2.0");
136     * </pre>
137     *
138     * @param settings font variation settings.
139     * @return FontVariationAxis[] the array of parsed font variation axis. {@code null} if settings
140     *                             has no font variation settings.
141     * @throws InvalidFormatException If given string is not a valid font variation settings format.
142     */
143    public static @Nullable FontVariationAxis[] fromFontVariationSettings(@Nullable String settings)
144            throws InvalidFormatException {
145        if (settings == null || settings.isEmpty()) {
146            return null;
147        }
148        final ArrayList<FontVariationAxis> axisList = new ArrayList<>();
149        final int length = settings.length();
150        for (int i = 0; i < length; i++) {
151            final char c = settings.charAt(i);
152            if (Character.isWhitespace(c)) {
153                continue;
154            }
155            if (!(c == '\'' || c == '"') || length < i + 6 || settings.charAt(i + 5) != c) {
156                throw new InvalidFormatException(
157                        "Tag should be wrapped with double or single quote: " + settings);
158            }
159            final String tagString = settings.substring(i + 1, i + 5);
160
161            i += 6;  // Move to end of tag.
162            int endOfValueString = settings.indexOf(',', i);
163            if (endOfValueString == -1) {
164                endOfValueString = length;
165            }
166            final float value;
167            try {
168                // Float.parseFloat ignores leading/trailing whitespaces.
169                value = Float.parseFloat(settings.substring(i, endOfValueString));
170            } catch (NumberFormatException e) {
171                throw new InvalidFormatException("Failed to parse float string: " + e.getMessage());
172            }
173            axisList.add(new FontVariationAxis(tagString, value));
174            i = endOfValueString;
175        }
176        if (axisList.isEmpty()) {
177            return null;
178        }
179        return axisList.toArray(new FontVariationAxis[0]);
180    }
181
182    /**
183     * Stringify the array of FontVariationAxis.
184     *
185     * @param axes an array of FontVariationAxis.
186     * @return String a valid font variation settings string.
187     */
188    public static @NonNull String toFontVariationSettings(@Nullable FontVariationAxis[] axes) {
189        if (axes == null || axes.length == 0) {
190            return "";
191        }
192        return TextUtils.join(",", axes);
193    }
194}
195
196