182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir/*
2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project
382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir *
482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Licensed under the Apache License, Version 2.0 (the "License");
582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * you may not use this file except in compliance with the License.
682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * You may obtain a copy of the License at
782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir *
882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir *      http://www.apache.org/licenses/LICENSE-2.0
982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir *
1082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Unless required by applicable law or agreed to in writing, software
1182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * distributed under the License is distributed on an "AS IS" BASIS,
1282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * See the License for the specific language governing permissions and
1482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * limitations under the License.
1582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */
16ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.emoji.text;
1782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
18ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
1982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
2082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.graphics.Paint;
2138746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport android.text.style.ReplacementSpan;
2238746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikas
23ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.NonNull;
24ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.RequiresApi;
25ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.RestrictTo;
26ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.VisibleForTesting;
27ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.util.Preconditions;
2882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
2982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir/**
3082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Base span class for the emoji replacement. When an emoji is found and needs to be replaced in a
3182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * CharSequence, an instance of this class is added to the CharSequence.
3282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */
3377b5c5b734f9f665577d1e3d178615db43ae1d4fSiyamed Sinir@RequiresApi(19)
3482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirpublic abstract class EmojiSpan extends ReplacementSpan {
3582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
3682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
3782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Temporary object to calculate the size of the span.
3882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
3982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private final Paint.FontMetricsInt mTmpFontMetrics = new Paint.FontMetricsInt();
4082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
4182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
4282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Information about emoji. This is not parcelled since we do not want multiple objects
4382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * representing same emoji to be in memory. When unparcelled, EmojiSpan tries to set it back
4482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * using the singleton EmojiCompat instance.
4582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
4682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private final EmojiMetadata mMetadata;
4782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
4882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
4982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Cached width of the span. Width is calculated according to the font metrics.
5082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
5182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private short mWidth = -1;
5282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
5382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
5482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Cached height of the span. Height is calculated according to the font metrics.
5582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
5682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private short mHeight = -1;
5782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
5882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
5982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Cached ratio of current font height to emoji image height.
6082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
6182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private float mRatio = 1.0f;
6282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
6382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
6482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Default constructor.
6582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
6682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param metadata information about the emoji, cannot be {@code null}
6782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
6882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
6982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
7082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
7182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    EmojiSpan(@NonNull final EmojiMetadata metadata) {
7282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        Preconditions.checkNotNull(metadata, "metadata cannot be null");
7382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mMetadata = metadata;
7482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
7582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
7682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @Override
7782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    public int getSize(@NonNull final Paint paint, final CharSequence text, final int start,
7882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int end, final Paint.FontMetricsInt fm) {
7982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        paint.getFontMetricsInt(mTmpFontMetrics);
8082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int fontHeight = Math.abs(mTmpFontMetrics.descent - mTmpFontMetrics.ascent);
8182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
8282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mRatio = fontHeight * 1.0f / mMetadata.getHeight();
8382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mHeight = (short) (mMetadata.getHeight() * mRatio);
8482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mWidth = (short) (mMetadata.getWidth() * mRatio);
8582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
8682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (fm != null) {
8782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            fm.ascent = mTmpFontMetrics.ascent;
8882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            fm.descent = mTmpFontMetrics.descent;
8982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            fm.top = mTmpFontMetrics.top;
9082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            fm.bottom = mTmpFontMetrics.bottom;
9182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
9282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
9382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return mWidth;
9482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
9582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
9682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
9782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
9882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
9982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
10082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    final EmojiMetadata getMetadata() {
10182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return mMetadata;
10282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
10382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
10482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
10582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return width of the span
10682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
10782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
10882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
10982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
11082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    final int getWidth() {
11182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return mWidth;
11282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
11382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
11482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
11582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return height of the span
11682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
11782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
11882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
11982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
12082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    final int getHeight() {
12182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return mHeight;
12282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
12382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
12482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
12582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
12682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
12782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
12882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    final float getRatio() {
12982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return mRatio;
13082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
13182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
13282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
13382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return unique id for the emoji that this EmojiSpan is used for
13482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
13582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
13682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
13782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
13882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @VisibleForTesting
13982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    public final int getId() {
14082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return getMetadata().getId();
14182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
14282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir}
143