• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.graphics.Canvas;
21 import android.graphics.Paint;
22 import android.graphics.Typeface;
23 import android.support.annotation.AnyThread;
24 import android.support.annotation.IntDef;
25 import android.support.annotation.IntRange;
26 import android.support.annotation.NonNull;
27 import android.support.annotation.RequiresApi;
28 import android.support.annotation.RestrictTo;
29 import android.support.text.emoji.flatbuffer.MetadataItem;
30 import android.support.text.emoji.flatbuffer.MetadataList;
31 
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 
35 /**
36  * Information about a single emoji.
37  *
38  * @hide
39  */
40 @RestrictTo(LIBRARY_GROUP)
41 @AnyThread
42 @RequiresApi(19)
43 public class EmojiMetadata {
44     /**
45      * Defines whether the system can render the emoji.
46      */
47     @IntDef({HAS_GLYPH_UNKNOWN, HAS_GLYPH_ABSENT, HAS_GLYPH_EXISTS})
48     @Retention(RetentionPolicy.SOURCE)
49     public @interface HasGlyph {
50     }
51 
52     /**
53      * Not calculated on device yet.
54      */
55     public static final int HAS_GLYPH_UNKNOWN = 0;
56 
57     /**
58      * Device cannot render the emoji.
59      */
60     public static final int HAS_GLYPH_ABSENT = 1;
61 
62     /**
63      * Device can render the emoji.
64      */
65     public static final int HAS_GLYPH_EXISTS = 2;
66 
67     /**
68      * @see #getMetadataItem()
69      */
70     private static final ThreadLocal<MetadataItem> sMetadataItem = new ThreadLocal<>();
71 
72     /**
73      * Index of the EmojiMetadata in {@link MetadataList}.
74      */
75     private final int mIndex;
76 
77     /**
78      * MetadataRepo that holds this instance.
79      */
80     private final MetadataRepo mMetadataRepo;
81 
82     /**
83      * Whether the system can render the emoji. Calculated at runtime on the device.
84      */
85     @HasGlyph
86     private volatile int mHasGlyph = HAS_GLYPH_UNKNOWN;
87 
EmojiMetadata(@onNull final MetadataRepo metadataRepo, @IntRange(from = 0) final int index)88     EmojiMetadata(@NonNull final MetadataRepo metadataRepo, @IntRange(from = 0) final int index) {
89         mMetadataRepo = metadataRepo;
90         mIndex = index;
91     }
92 
93     /**
94      * Draws the emoji represented by this EmojiMetadata onto a canvas with origin at (x,y), using
95      * the specified paint.
96      *
97      * @param canvas Canvas to be drawn
98      * @param x x-coordinate of the origin of the emoji being drawn
99      * @param y y-coordinate of the baseline of the emoji being drawn
100      * @param paint Paint used for the text (e.g. color, size, style)
101      */
draw(@onNull final Canvas canvas, final float x, final float y, @NonNull final Paint paint)102     public void draw(@NonNull final Canvas canvas, final float x, final float y,
103             @NonNull final Paint paint) {
104         final Typeface typeface = mMetadataRepo.getTypeface();
105         final Typeface oldTypeface = paint.getTypeface();
106         paint.setTypeface(typeface);
107         // MetadataRepo.getEmojiCharArray() is a continous array of chars that is used to store the
108         // chars for emojis. since all emojis are mapped to a single codepoint, and since it is 2
109         // chars wide, we assume that the start index of the current emoji is mIndex * 2, and it is
110         // 2 chars long.
111         final int charArrayStartIndex = mIndex * 2;
112         canvas.drawText(mMetadataRepo.getEmojiCharArray(), charArrayStartIndex, 2, x, y, paint);
113         paint.setTypeface(oldTypeface);
114     }
115 
116     /**
117      * @return a ThreadLocal instance of MetadataItem for this EmojiMetadata
118      */
getMetadataItem()119     private MetadataItem getMetadataItem() {
120         MetadataItem result = sMetadataItem.get();
121         if (result == null) {
122             result = new MetadataItem();
123             sMetadataItem.set(result);
124         }
125         // MetadataList is a wrapper around the metadata ByteBuffer. MetadataItem is a wrapper with
126         // an index (pointer) on this ByteBuffer that represents a single emoji. Both are FlatBuffer
127         // classes that wraps a ByteBuffer and gives access to the information in it. In order not
128         // to create a wrapper class for each EmojiMetadata, we use mIndex as the index of the
129         // MetadataItem in the ByteBuffer. We need to reiniitalize the current thread local instance
130         // by executing the statement below. All the statement does is to set an int index in
131         // MetadataItem. the same instance is used by all EmojiMetadata classes in the same thread.
132         mMetadataRepo.getMetadataList().list(result, mIndex);
133         return result;
134     }
135 
136     /**
137      * @return unique id for the emoji
138      */
getId()139     public int getId() {
140         return getMetadataItem().id();
141     }
142 
143     /**
144      * @return width of the emoji image
145      */
getWidth()146     public short getWidth() {
147         return getMetadataItem().width();
148     }
149 
150     /**
151      * @return height of the emoji image
152      */
getHeight()153     public short getHeight() {
154         return getMetadataItem().height();
155     }
156 
157     /**
158      * @return in which metadata version the emoji was added to metadata
159      */
getCompatAdded()160     public short getCompatAdded() {
161         return getMetadataItem().compatAdded();
162     }
163 
164     /**
165      * @return first SDK that the support for this emoji was added
166      */
getSdkAdded()167     public short getSdkAdded() {
168         return getMetadataItem().sdkAdded();
169     }
170 
171     /**
172      * @return whether the emoji is in Emoji Presentation by default (without emoji
173      * style selector 0xFE0F)
174      */
175     @HasGlyph
getHasGlyph()176     public int getHasGlyph() {
177         return mHasGlyph;
178     }
179 
180     /**
181      * Set whether the system can render the emoji.
182      *
183      * @param hasGlyph {@code true} if system can render the emoji
184      */
setHasGlyph(boolean hasGlyph)185     public void setHasGlyph(boolean hasGlyph) {
186         mHasGlyph = hasGlyph ? HAS_GLYPH_EXISTS : HAS_GLYPH_ABSENT;
187     }
188 
189     /**
190      * @return whether the emoji is in Emoji Presentation by default (without emoji
191      *         style selector 0xFE0F)
192      */
isDefaultEmoji()193     public boolean isDefaultEmoji() {
194         return getMetadataItem().emojiStyle();
195     }
196 
197     /**
198      * @param index index of the codepoint
199      *
200      * @return the codepoint at index
201      */
getCodepointAt(int index)202     public int getCodepointAt(int index) {
203         return getMetadataItem().codepoints(index);
204     }
205 
206     /**
207      * @return the length of the codepoints for this emoji
208      */
getCodepointsLength()209     public int getCodepointsLength() {
210         return getMetadataItem().codepointsLength();
211     }
212 
213 }
214