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