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