Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright 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 package androidx.emoji.text;
     17 
     18 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     19 
     20 import android.content.res.AssetManager;
     21 
     22 import androidx.annotation.AnyThread;
     23 import androidx.annotation.IntRange;
     24 import androidx.annotation.RequiresApi;
     25 import androidx.annotation.RestrictTo;
     26 import androidx.text.emoji.flatbuffer.MetadataList;
     27 
     28 import java.io.IOException;
     29 import java.io.InputStream;
     30 import java.nio.ByteBuffer;
     31 import java.nio.ByteOrder;
     32 
     33 /**
     34  * Reads the emoji metadata from a given InputStream or ByteBuffer.
     35  *
     36  * @hide
     37  */
     38 @RestrictTo(LIBRARY_GROUP)
     39 @AnyThread
     40 @RequiresApi(19)
     41 class MetadataListReader {
     42 
     43     /**
     44      * Meta tag for emoji metadata. This string is used by the font update script to insert the
     45      * emoji meta into the font. This meta table contains the list of all emojis which are stored in
     46      * binary format using FlatBuffers. This flat list is later converted by the system into a trie.
     47      * {@code int} representation for "Emji"
     48      *
     49      * @see MetadataRepo
     50      */
     51     private static final int EMJI_TAG = 'E' << 24 | 'm' << 16 | 'j' << 8 | 'i';
     52 
     53     /**
     54      * Deprecated meta tag name. Do not use, kept for compatibility reasons, will be removed soon.
     55      */
     56     private static final int EMJI_TAG_DEPRECATED = 'e' << 24 | 'm' << 16 | 'j' << 8 | 'i';
     57 
     58     /**
     59      * The name of the meta table in the font. int representation for "meta"
     60      */
     61     private static final int META_TABLE_NAME = 'm' << 24 | 'e' << 16 | 't' << 8 | 'a';
     62 
     63     /**
     64      * Construct MetadataList from an input stream. Does not close the given InputStream, therefore
     65      * it is caller's responsibility to properly close the stream.
     66      *
     67      * @param inputStream InputStream to read emoji metadata from
     68      */
     69     static MetadataList read(InputStream inputStream) throws IOException {
     70         final OpenTypeReader openTypeReader = new InputStreamOpenTypeReader(inputStream);
     71         final OffsetInfo offsetInfo = findOffsetInfo(openTypeReader);
     72         // skip to where metadata is
     73         openTypeReader.skip((int) (offsetInfo.getStartOffset() - openTypeReader.getPosition()));
     74         // allocate a ByteBuffer and read into it since FlatBuffers can read only from a ByteBuffer
     75         final ByteBuffer buffer = ByteBuffer.allocate((int) offsetInfo.getLength());
     76         final int numRead = inputStream.read(buffer.array());
     77         if (numRead != offsetInfo.getLength()) {
     78             throw new IOException("Needed " + offsetInfo.getLength() + " bytes, got " + numRead);
     79         }
     80 
     81         return MetadataList.getRootAsMetadataList(buffer);
     82     }
     83 
     84     /**
     85      * Construct MetadataList from a byte buffer.
     86      *
     87      * @param byteBuffer ByteBuffer to read emoji metadata from
     88      */
     89     static MetadataList read(final ByteBuffer byteBuffer) throws IOException {
     90         final ByteBuffer newBuffer = byteBuffer.duplicate();
     91         final OpenTypeReader reader = new ByteBufferReader(newBuffer);
     92         final OffsetInfo offsetInfo = findOffsetInfo(reader);
     93         // skip to where metadata is
     94         newBuffer.position((int) offsetInfo.getStartOffset());
     95         return MetadataList.getRootAsMetadataList(newBuffer);
     96     }
     97 
     98     /**
     99      * Construct MetadataList from an asset.
    100      *
    101      * @param assetManager AssetManager instance
    102      * @param assetPath asset manager path of the file that the Typeface and metadata will be
    103      *                  created from
    104      */
    105     static MetadataList read(AssetManager assetManager, String assetPath)
    106             throws IOException {
    107         try (InputStream inputStream = assetManager.open(assetPath)) {
    108             return read(inputStream);
    109         }
    110     }
    111 
    112     /**
    113      * Finds the start offset and length of the emoji metadata in the font.
    114      *
    115      * @return OffsetInfo which contains start offset and length of the emoji metadata in the font
    116      *
    117      * @throws IOException
    118      */
    119     private static OffsetInfo findOffsetInfo(OpenTypeReader reader) throws IOException {
    120         // skip sfnt version
    121         reader.skip(OpenTypeReader.UINT32_BYTE_COUNT);
    122         // start of Table Count
    123         final int tableCount = reader.readUnsignedShort();
    124         if (tableCount > 100) {
    125             //something is wrong quit
    126             throw new IOException("Cannot read metadata.");
    127         }
    128         //skip to begining of tables data
    129         reader.skip(OpenTypeReader.UINT16_BYTE_COUNT * 3);
    130 
    131         long metaOffset = -1;
    132         for (int i = 0; i < tableCount; i++) {
    133             final int tag = reader.readTag();
    134             // skip checksum
    135             reader.skip(OpenTypeReader.UINT32_BYTE_COUNT);
    136             final long offset = reader.readUnsignedInt();
    137             // skip mLength
    138             reader.skip(OpenTypeReader.UINT32_BYTE_COUNT);
    139             if (META_TABLE_NAME == tag) {
    140                 metaOffset = offset;
    141                 break;
    142             }
    143         }
    144 
    145         if (metaOffset != -1) {
    146             // skip to the begining of meta tables.
    147             reader.skip((int) (metaOffset - reader.getPosition()));
    148             // skip minorVersion, majorVersion, flags, reserved,
    149             reader.skip(
    150                     OpenTypeReader.UINT16_BYTE_COUNT * 2 + OpenTypeReader.UINT32_BYTE_COUNT * 2);
    151             final long mapsCount = reader.readUnsignedInt();
    152             for (int i = 0; i < mapsCount; i++) {
    153                 final int tag = reader.readTag();
    154                 final long dataOffset = reader.readUnsignedInt();
    155                 final long dataLength = reader.readUnsignedInt();
    156                 if (EMJI_TAG == tag || EMJI_TAG_DEPRECATED == tag) {
    157                     return new OffsetInfo(dataOffset + metaOffset, dataLength);
    158                 }
    159             }
    160         }
    161 
    162         throw new IOException("Cannot read metadata.");
    163     }
    164 
    165     /**
    166      * Start offset and length of the emoji metadata in the font.
    167      */
    168     private static class OffsetInfo {
    169         private final long mStartOffset;
    170         private final long mLength;
    171 
    172         OffsetInfo(long startOffset, long length) {
    173             mStartOffset = startOffset;
    174             mLength = length;
    175         }
    176 
    177         long getStartOffset() {
    178             return mStartOffset;
    179         }
    180 
    181         long getLength() {
    182             return mLength;
    183         }
    184     }
    185 
    186     private static int toUnsignedShort(final short value) {
    187         return value & 0xFFFF;
    188     }
    189 
    190     private static long toUnsignedInt(final int value) {
    191         return value & 0xFFFFFFFFL;
    192     }
    193 
    194     private interface OpenTypeReader {
    195         int UINT16_BYTE_COUNT = 2;
    196         int UINT32_BYTE_COUNT = 4;
    197 
    198         /**
    199          * Reads an {@code OpenType uint16}.
    200          *
    201          * @throws IOException
    202          */
    203         int readUnsignedShort() throws IOException;
    204 
    205         /**
    206          * Reads an {@code OpenType uint32}.
    207          *
    208          * @throws IOException
    209          */
    210         long readUnsignedInt() throws IOException;
    211 
    212         /**
    213          * Reads an {@code OpenType Tag}.
    214          *
    215          * @throws IOException
    216          */
    217         int readTag() throws IOException;
    218 
    219         /**
    220          * Skip the given amount of numOfBytes
    221          *
    222          * @throws IOException
    223          */
    224         void skip(int numOfBytes) throws IOException;
    225 
    226         /**
    227          * @return the position of the reader
    228          */
    229         long getPosition();
    230     }
    231 
    232     /**
    233      * Reads {@code OpenType} data from an {@link InputStream}.
    234      */
    235     private static class InputStreamOpenTypeReader implements OpenTypeReader {
    236 
    237         private final byte[] mByteArray;
    238         private final ByteBuffer mByteBuffer;
    239         private final InputStream mInputStream;
    240         private long mPosition = 0;
    241 
    242         /**
    243          * Constructs the reader with the given InputStream. Does not close the InputStream, it is
    244          * caller's responsibility to close it.
    245          *
    246          * @param inputStream InputStream to read from
    247          */
    248         InputStreamOpenTypeReader(final InputStream inputStream) {
    249             mInputStream = inputStream;
    250             mByteArray = new byte[UINT32_BYTE_COUNT];
    251             mByteBuffer = ByteBuffer.wrap(mByteArray);
    252             mByteBuffer.order(ByteOrder.BIG_ENDIAN);
    253         }
    254 
    255         @Override
    256         public int readUnsignedShort() throws IOException {
    257             mByteBuffer.position(0);
    258             read(UINT16_BYTE_COUNT);
    259             return toUnsignedShort(mByteBuffer.getShort());
    260         }
    261 
    262         @Override
    263         public long readUnsignedInt() throws IOException {
    264             mByteBuffer.position(0);
    265             read(UINT32_BYTE_COUNT);
    266             return toUnsignedInt(mByteBuffer.getInt());
    267         }
    268 
    269         @Override
    270         public int readTag() throws IOException {
    271             mByteBuffer.position(0);
    272             read(UINT32_BYTE_COUNT);
    273             return mByteBuffer.getInt();
    274         }
    275 
    276         @Override
    277         public void skip(int numOfBytes) throws IOException {
    278             while (numOfBytes > 0) {
    279                 int skipped = (int) mInputStream.skip(numOfBytes);
    280                 if (skipped < 1) {
    281                     throw new IOException("Skip didn't move at least 1 byte forward");
    282                 }
    283                 numOfBytes -= skipped;
    284                 mPosition += skipped;
    285             }
    286         }
    287 
    288         @Override
    289         public long getPosition() {
    290             return mPosition;
    291         }
    292 
    293         private void read(@IntRange(from = 0, to = UINT32_BYTE_COUNT) final int numOfBytes)
    294                 throws IOException {
    295             if (mInputStream.read(mByteArray, 0, numOfBytes) != numOfBytes) {
    296                 throw new IOException("read failed");
    297             }
    298             mPosition += numOfBytes;
    299         }
    300     }
    301 
    302     /**
    303      * Reads OpenType data from a ByteBuffer.
    304      */
    305     private static class ByteBufferReader implements OpenTypeReader {
    306 
    307         private final ByteBuffer mByteBuffer;
    308 
    309         /**
    310          * Constructs the reader with the given ByteBuffer.
    311          *
    312          * @param byteBuffer ByteBuffer to read from
    313          */
    314         ByteBufferReader(final ByteBuffer byteBuffer) {
    315             mByteBuffer = byteBuffer;
    316             mByteBuffer.order(ByteOrder.BIG_ENDIAN);
    317         }
    318 
    319         @Override
    320         public int readUnsignedShort() throws IOException {
    321             return toUnsignedShort(mByteBuffer.getShort());
    322         }
    323 
    324         @Override
    325         public long readUnsignedInt() throws IOException {
    326             return toUnsignedInt(mByteBuffer.getInt());
    327         }
    328 
    329         @Override
    330         public int readTag() throws IOException {
    331             return mByteBuffer.getInt();
    332         }
    333 
    334         @Override
    335         public void skip(final int numOfBytes) throws IOException {
    336             mByteBuffer.position(mByteBuffer.position() + numOfBytes);
    337         }
    338 
    339         @Override
    340         public long getPosition() {
    341             return mByteBuffer.position();
    342         }
    343     }
    344 
    345     private MetadataListReader() {
    346     }
    347 }
    348