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      */
read(InputStream inputStream)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      */
read(final ByteBuffer byteBuffer)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      */
read(AssetManager assetManager, String assetPath)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      */
findOffsetInfo(OpenTypeReader reader)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 
OffsetInfo(long startOffset, long length)172         OffsetInfo(long startOffset, long length) {
173             mStartOffset = startOffset;
174             mLength = length;
175         }
176 
getStartOffset()177         long getStartOffset() {
178             return mStartOffset;
179         }
180 
getLength()181         long getLength() {
182             return mLength;
183         }
184     }
185 
toUnsignedShort(final short value)186     private static int toUnsignedShort(final short value) {
187         return value & 0xFFFF;
188     }
189 
toUnsignedInt(final int value)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          */
readUnsignedShort()203         int readUnsignedShort() throws IOException;
204 
205         /**
206          * Reads an {@code OpenType uint32}.
207          *
208          * @throws IOException
209          */
readUnsignedInt()210         long readUnsignedInt() throws IOException;
211 
212         /**
213          * Reads an {@code OpenType Tag}.
214          *
215          * @throws IOException
216          */
readTag()217         int readTag() throws IOException;
218 
219         /**
220          * Skip the given amount of numOfBytes
221          *
222          * @throws IOException
223          */
skip(int numOfBytes)224         void skip(int numOfBytes) throws IOException;
225 
226         /**
227          * @return the position of the reader
228          */
getPosition()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          */
InputStreamOpenTypeReader(final InputStream inputStream)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
readUnsignedShort()256         public int readUnsignedShort() throws IOException {
257             mByteBuffer.position(0);
258             read(UINT16_BYTE_COUNT);
259             return toUnsignedShort(mByteBuffer.getShort());
260         }
261 
262         @Override
readUnsignedInt()263         public long readUnsignedInt() throws IOException {
264             mByteBuffer.position(0);
265             read(UINT32_BYTE_COUNT);
266             return toUnsignedInt(mByteBuffer.getInt());
267         }
268 
269         @Override
readTag()270         public int readTag() throws IOException {
271             mByteBuffer.position(0);
272             read(UINT32_BYTE_COUNT);
273             return mByteBuffer.getInt();
274         }
275 
276         @Override
skip(int numOfBytes)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
getPosition()289         public long getPosition() {
290             return mPosition;
291         }
292 
read(@ntRangefrom = 0, to = UINT32_BYTE_COUNT) final int numOfBytes)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          */
ByteBufferReader(final ByteBuffer byteBuffer)314         ByteBufferReader(final ByteBuffer byteBuffer) {
315             mByteBuffer = byteBuffer;
316             mByteBuffer.order(ByteOrder.BIG_ENDIAN);
317         }
318 
319         @Override
readUnsignedShort()320         public int readUnsignedShort() throws IOException {
321             return toUnsignedShort(mByteBuffer.getShort());
322         }
323 
324         @Override
readUnsignedInt()325         public long readUnsignedInt() throws IOException {
326             return toUnsignedInt(mByteBuffer.getInt());
327         }
328 
329         @Override
readTag()330         public int readTag() throws IOException {
331             return mByteBuffer.getInt();
332         }
333 
334         @Override
skip(final int numOfBytes)335         public void skip(final int numOfBytes) throws IOException {
336             mByteBuffer.position(mByteBuffer.position() + numOfBytes);
337         }
338 
339         @Override
getPosition()340         public long getPosition() {
341             return mByteBuffer.position();
342         }
343     }
344 
MetadataListReader()345     private MetadataListReader() {
346     }
347 }
348