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