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