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