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