mirror of https://github.com/M66B/FairEmail.git
276 lines
8.4 KiB
Java
276 lines
8.4 KiB
Java
![]() |
/*
|
||
|
* Copyright 2021 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
package androidx.emoji2.text;
|
||
|
|
||
|
import android.content.res.AssetManager;
|
||
|
import android.graphics.Typeface;
|
||
|
import android.util.SparseArray;
|
||
|
|
||
|
import androidx.annotation.AnyThread;
|
||
|
import androidx.annotation.NonNull;
|
||
|
import androidx.annotation.RequiresApi;
|
||
|
import androidx.annotation.RestrictTo;
|
||
|
import androidx.annotation.VisibleForTesting;
|
||
|
import androidx.core.os.TraceCompat;
|
||
|
import androidx.core.util.Preconditions;
|
||
|
import androidx.emoji2.text.flatbuffer.MetadataList;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
import java.io.InputStream;
|
||
|
import java.nio.ByteBuffer;
|
||
|
|
||
|
/**
|
||
|
* Class to hold the emoji metadata required to process and draw emojis.
|
||
|
*/
|
||
|
@AnyThread
|
||
|
@RequiresApi(19)
|
||
|
public final class MetadataRepo {
|
||
|
/**
|
||
|
* The default children size of the root node.
|
||
|
*/
|
||
|
private static final int DEFAULT_ROOT_SIZE = 1024;
|
||
|
private static final String S_TRACE_CREATE_REPO = "EmojiCompat.MetadataRepo.create";
|
||
|
|
||
|
/**
|
||
|
* MetadataList that contains the emoji metadata.
|
||
|
*/
|
||
|
private final @NonNull MetadataList mMetadataList;
|
||
|
|
||
|
/**
|
||
|
* char presentation of all TypefaceEmojiRasterizer's in a single array. All emojis we have are
|
||
|
* mapped to Private Use Area A, in the range U+F0000..U+FFFFD. Therefore each emoji takes 2
|
||
|
* chars.
|
||
|
*/
|
||
|
private final @NonNull char[] mEmojiCharArray;
|
||
|
|
||
|
/**
|
||
|
* Empty root node of the trie.
|
||
|
*/
|
||
|
private final @NonNull Node mRootNode;
|
||
|
|
||
|
/**
|
||
|
* Typeface to be used to render emojis.
|
||
|
*/
|
||
|
private final @NonNull Typeface mTypeface;
|
||
|
|
||
|
/**
|
||
|
* Private constructor that is called by one of {@code create} methods.
|
||
|
*
|
||
|
* @param typeface Typeface to be used to render emojis
|
||
|
* @param metadataList MetadataList that contains the emoji metadata
|
||
|
*/
|
||
|
private MetadataRepo(@NonNull final Typeface typeface,
|
||
|
@NonNull final MetadataList metadataList) {
|
||
|
mTypeface = typeface;
|
||
|
mMetadataList = metadataList;
|
||
|
mRootNode = new Node(DEFAULT_ROOT_SIZE);
|
||
|
mEmojiCharArray = new char[mMetadataList.listLength() * 2];
|
||
|
constructIndex(mMetadataList);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct MetadataRepo with empty metadata.
|
||
|
*
|
||
|
* This should only be used from tests.
|
||
|
* @hide
|
||
|
*/
|
||
|
@NonNull
|
||
|
@RestrictTo(RestrictTo.Scope.TESTS)
|
||
|
public static MetadataRepo create(@NonNull final Typeface typeface) {
|
||
|
try {
|
||
|
TraceCompat.beginSection(S_TRACE_CREATE_REPO);
|
||
|
return new MetadataRepo(typeface, new MetadataList());
|
||
|
} finally {
|
||
|
TraceCompat.endSection();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct MetadataRepo from an input stream. The library does not close the given
|
||
|
* InputStream, therefore it is caller's responsibility to properly close the stream.
|
||
|
*
|
||
|
* @param typeface Typeface to be used to render emojis
|
||
|
* @param inputStream InputStream to read emoji metadata from
|
||
|
*/
|
||
|
@NonNull
|
||
|
public static MetadataRepo create(@NonNull final Typeface typeface,
|
||
|
@NonNull final InputStream inputStream) throws IOException {
|
||
|
try {
|
||
|
TraceCompat.beginSection(S_TRACE_CREATE_REPO);
|
||
|
return new MetadataRepo(typeface, MetadataListReader.read(inputStream));
|
||
|
} finally {
|
||
|
TraceCompat.endSection();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct MetadataRepo from a byte buffer. The position of the ByteBuffer will change, it is
|
||
|
* caller's responsibility to reposition the buffer if required.
|
||
|
*
|
||
|
* @param typeface Typeface to be used to render emojis
|
||
|
* @param byteBuffer ByteBuffer to read emoji metadata from
|
||
|
*/
|
||
|
@NonNull
|
||
|
public static MetadataRepo create(@NonNull final Typeface typeface,
|
||
|
@NonNull final ByteBuffer byteBuffer) throws IOException {
|
||
|
try {
|
||
|
TraceCompat.beginSection(S_TRACE_CREATE_REPO);
|
||
|
return new MetadataRepo(typeface, MetadataListReader.read(byteBuffer));
|
||
|
} finally {
|
||
|
TraceCompat.endSection();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct MetadataRepo from an asset.
|
||
|
*
|
||
|
* @param assetManager AssetManager instance
|
||
|
* @param assetPath asset manager path of the file that the Typeface and metadata will be
|
||
|
* created from
|
||
|
*/
|
||
|
@NonNull
|
||
|
public static MetadataRepo create(@NonNull final AssetManager assetManager,
|
||
|
@NonNull final String assetPath) throws IOException {
|
||
|
try {
|
||
|
TraceCompat.beginSection(S_TRACE_CREATE_REPO);
|
||
|
final Typeface typeface = Typeface.createFromAsset(assetManager, assetPath);
|
||
|
return new MetadataRepo(typeface,
|
||
|
MetadataListReader.read(assetManager, assetPath));
|
||
|
} finally {
|
||
|
TraceCompat.endSection();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read emoji metadata list and construct the trie.
|
||
|
*/
|
||
|
private void constructIndex(final MetadataList metadataList) {
|
||
|
int length = metadataList.listLength();
|
||
|
for (int i = 0; i < length; i++) {
|
||
|
final TypefaceEmojiRasterizer metadata = new TypefaceEmojiRasterizer(this, i);
|
||
|
//since all emojis are mapped to a single codepoint in Private Use Area A they are 2
|
||
|
//chars wide
|
||
|
//noinspection ResultOfMethodCallIgnored
|
||
|
Character.toChars(metadata.getId(), mEmojiCharArray, i * 2);
|
||
|
put(metadata);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@NonNull
|
||
|
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||
|
Typeface getTypeface() {
|
||
|
return mTypeface;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||
|
int getMetadataVersion() {
|
||
|
return mMetadataList.version();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@NonNull
|
||
|
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||
|
Node getRootNode() {
|
||
|
return mRootNode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@NonNull
|
||
|
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||
|
public char[] getEmojiCharArray() {
|
||
|
return mEmojiCharArray;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@NonNull
|
||
|
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||
|
public MetadataList getMetadataList() {
|
||
|
return mMetadataList;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a TypefaceEmojiRasterizer to the index.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||
|
@VisibleForTesting
|
||
|
void put(@NonNull final TypefaceEmojiRasterizer data) {
|
||
|
Preconditions.checkNotNull(data, "emoji metadata cannot be null");
|
||
|
Preconditions.checkArgument(data.getCodepointsLength() > 0,
|
||
|
"invalid metadata codepoint length");
|
||
|
|
||
|
mRootNode.put(data, 0, data.getCodepointsLength() - 1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Trie node that holds mapping from emoji codepoint(s) to TypefaceEmojiRasterizer.
|
||
|
*
|
||
|
* A single codepoint emoji is represented by a child of the root node.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||
|
static class Node {
|
||
|
private final SparseArray<Node> mChildren;
|
||
|
private TypefaceEmojiRasterizer mData;
|
||
|
|
||
|
private Node() {
|
||
|
this(1);
|
||
|
}
|
||
|
|
||
|
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||
|
Node(final int defaultChildrenSize) {
|
||
|
mChildren = new SparseArray<>(defaultChildrenSize);
|
||
|
}
|
||
|
|
||
|
Node get(final int key) {
|
||
|
return mChildren == null ? null : mChildren.get(key);
|
||
|
}
|
||
|
|
||
|
final TypefaceEmojiRasterizer getData() {
|
||
|
return mData;
|
||
|
}
|
||
|
|
||
|
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||
|
void put(@NonNull final TypefaceEmojiRasterizer data, final int start, final int end) {
|
||
|
Node node = get(data.getCodepointAt(start));
|
||
|
if (node == null) {
|
||
|
node = new Node();
|
||
|
mChildren.put(data.getCodepointAt(start), node);
|
||
|
}
|
||
|
|
||
|
if (end > start) {
|
||
|
node.put(data, start + 1, end);
|
||
|
} else {
|
||
|
node.mData = data;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|