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 */ 16package android.support.text.emoji; 17 18import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 19 20import android.content.res.AssetManager; 21import android.graphics.Typeface; 22import android.support.annotation.AnyThread; 23import android.support.annotation.NonNull; 24import android.support.annotation.RequiresApi; 25import android.support.annotation.RestrictTo; 26import android.support.annotation.VisibleForTesting; 27import android.support.text.emoji.flatbuffer.MetadataList; 28import android.support.v4.util.Preconditions; 29import android.util.SparseArray; 30 31import java.io.IOException; 32import java.io.InputStream; 33import java.nio.ByteBuffer; 34 35/** 36 * Class to hold the emoji metadata required to process and draw emojis. 37 */ 38@AnyThread 39@RequiresApi(19) 40public final class MetadataRepo { 41 /** 42 * The default children size of the root node. 43 */ 44 private static final int DEFAULT_ROOT_SIZE = 1024; 45 46 /** 47 * MetadataList that contains the emoji metadata. 48 */ 49 private final MetadataList mMetadataList; 50 51 /** 52 * char presentation of all EmojiMetadata's in a single array. All emojis we have are mapped to 53 * Private Use Area A, in the range U+F0000..U+FFFFD. Therefore each emoji takes 2 chars. 54 */ 55 private final char[] mEmojiCharArray; 56 57 /** 58 * Empty root node of the trie. 59 */ 60 private final Node mRootNode; 61 62 /** 63 * Typeface to be used to render emojis. 64 */ 65 private final Typeface mTypeface; 66 67 /** 68 * Constructor used for tests. 69 * 70 * @hide 71 */ 72 @RestrictTo(LIBRARY_GROUP) 73 MetadataRepo() { 74 mTypeface = null; 75 mMetadataList = null; 76 mRootNode = new Node(DEFAULT_ROOT_SIZE); 77 mEmojiCharArray = new char[0]; 78 } 79 80 /** 81 * Private constructor that is called by one of {@code create} methods. 82 * 83 * @param typeface Typeface to be used to render emojis 84 * @param metadataList MetadataList that contains the emoji metadata 85 */ 86 private MetadataRepo(@NonNull final Typeface typeface, 87 @NonNull final MetadataList metadataList) { 88 mTypeface = typeface; 89 mMetadataList = metadataList; 90 mRootNode = new Node(DEFAULT_ROOT_SIZE); 91 mEmojiCharArray = new char[mMetadataList.listLength() * 2]; 92 constructIndex(mMetadataList); 93 } 94 95 /** 96 * Construct MetadataRepo from an input stream. The library does not close the given 97 * InputStream, therefore it is caller's responsibility to properly close the stream. 98 * 99 * @param typeface Typeface to be used to render emojis 100 * @param inputStream InputStream to read emoji metadata from 101 */ 102 public static MetadataRepo create(@NonNull final Typeface typeface, 103 @NonNull final InputStream inputStream) throws IOException { 104 return new MetadataRepo(typeface, MetadataListReader.read(inputStream)); 105 } 106 107 /** 108 * Construct MetadataRepo from a byte buffer. The position of the ByteBuffer will change, it is 109 * caller's responsibility to reposition the buffer if required. 110 * 111 * @param typeface Typeface to be used to render emojis 112 * @param byteBuffer ByteBuffer to read emoji metadata from 113 */ 114 public static MetadataRepo create(@NonNull final Typeface typeface, 115 @NonNull final ByteBuffer byteBuffer) throws IOException { 116 return new MetadataRepo(typeface, MetadataListReader.read(byteBuffer)); 117 } 118 119 /** 120 * Construct MetadataRepo from an asset. 121 * 122 * @param assetManager AssetManager instance 123 * @param assetPath asset manager path of the file that the Typeface and metadata will be 124 * created from 125 */ 126 public static MetadataRepo create(@NonNull final AssetManager assetManager, 127 final String assetPath) throws IOException { 128 final Typeface typeface = Typeface.createFromAsset(assetManager, assetPath); 129 return new MetadataRepo(typeface, MetadataListReader.read(assetManager, assetPath)); 130 } 131 132 /** 133 * Read emoji metadata list and construct the trie. 134 */ 135 private void constructIndex(final MetadataList metadataList) { 136 int length = metadataList.listLength(); 137 for (int i = 0; i < length; i++) { 138 final EmojiMetadata metadata = new EmojiMetadata(this, i); 139 //since all emojis are mapped to a single codepoint in Private Use Area A they are 2 140 //chars wide 141 //noinspection ResultOfMethodCallIgnored 142 Character.toChars(metadata.getId(), mEmojiCharArray, i * 2); 143 put(metadata); 144 } 145 } 146 147 /** 148 * @hide 149 */ 150 @RestrictTo(LIBRARY_GROUP) 151 Typeface getTypeface() { 152 return mTypeface; 153 } 154 155 /** 156 * @hide 157 */ 158 @RestrictTo(LIBRARY_GROUP) 159 int getMetadataVersion() { 160 return mMetadataList.version(); 161 } 162 163 /** 164 * @hide 165 */ 166 @RestrictTo(LIBRARY_GROUP) 167 Node getRootNode() { 168 return mRootNode; 169 } 170 171 /** 172 * @hide 173 */ 174 @RestrictTo(LIBRARY_GROUP) 175 public char[] getEmojiCharArray() { 176 return mEmojiCharArray; 177 } 178 179 /** 180 * @hide 181 */ 182 @RestrictTo(LIBRARY_GROUP) 183 public MetadataList getMetadataList() { 184 return mMetadataList; 185 } 186 187 /** 188 * Add an EmojiMetadata to the index. 189 * 190 * @hide 191 */ 192 @RestrictTo(LIBRARY_GROUP) 193 @VisibleForTesting 194 void put(@NonNull final EmojiMetadata data) { 195 Preconditions.checkNotNull(data, "emoji metadata cannot be null"); 196 Preconditions.checkArgument(data.getCodepointsLength() > 0, 197 "invalid metadata codepoint length"); 198 199 mRootNode.put(data, 0, data.getCodepointsLength() - 1); 200 } 201 202 /** 203 * Trie node that holds mapping from emoji codepoint(s) to EmojiMetadata. A single codepoint 204 * emoji is represented by a child of the root node. 205 * 206 * @hide 207 */ 208 @RestrictTo(LIBRARY_GROUP) 209 static class Node { 210 private final SparseArray<Node> mChildren; 211 private EmojiMetadata mData; 212 213 private Node() { 214 this(1); 215 } 216 217 private Node(final int defaultChildrenSize) { 218 mChildren = new SparseArray<>(defaultChildrenSize); 219 } 220 221 Node get(final int key) { 222 return mChildren == null ? null : mChildren.get(key); 223 } 224 225 final EmojiMetadata getData() { 226 return mData; 227 } 228 229 private void put(@NonNull final EmojiMetadata data, final int start, final int end) { 230 Node node = get(data.getCodepointAt(start)); 231 if (node == null) { 232 node = new Node(); 233 mChildren.put(data.getCodepointAt(start), node); 234 } 235 236 if (end > start) { 237 node.put(data, start + 1, end); 238 } else { 239 node.mData = data; 240 } 241 } 242 } 243} 244