182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir/*
282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Copyright (C) 2017 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 */
1682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirpackage android.support.text.emoji;
1782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
1882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
1982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
2082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.graphics.Paint;
2182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.support.annotation.NonNull;
2277b5c5b734f9f665577d1e3d178615db43ae1d4fSiyamed Sinirimport android.support.annotation.RequiresApi;
2382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.support.annotation.RestrictTo;
2482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.support.annotation.VisibleForTesting;
2582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.support.v4.util.Preconditions;
2682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.style.ReplacementSpan;
2782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
2882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir/**
2982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Base span class for the emoji replacement. When an emoji is found and needs to be replaced in a
3082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * CharSequence, an instance of this class is added to the CharSequence.
3182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */
3277b5c5b734f9f665577d1e3d178615db43ae1d4fSiyamed Sinir@RequiresApi(19)
3382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirpublic abstract class EmojiSpan extends ReplacementSpan {
3482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
3582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
3682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Temporary object to calculate the size of the span.
3782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
3882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private final Paint.FontMetricsInt mTmpFontMetrics = new Paint.FontMetricsInt();
3982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
4082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
4182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Information about emoji. This is not parcelled since we do not want multiple objects
4282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * representing same emoji to be in memory. When unparcelled, EmojiSpan tries to set it back
4382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * using the singleton EmojiCompat instance.
4482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
4582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private final EmojiMetadata mMetadata;
4682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
4782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
4882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Cached width of the span. Width is calculated according to the font metrics.
4982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
5082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private short mWidth = -1;
5182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
5282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
5382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Cached height of the span. Height is calculated according to the font metrics.
5482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
5582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private short mHeight = -1;
5682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
5782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
5882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Cached ratio of current font height to emoji image height.
5982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
6082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private float mRatio = 1.0f;
6182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
6282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
6382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Default constructor.
6482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
6582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param metadata information about the emoji, cannot be {@code null}
6682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
6782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
6882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
6982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
7082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    EmojiSpan(@NonNull final EmojiMetadata metadata) {
7182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        Preconditions.checkNotNull(metadata, "metadata cannot be null");
7282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mMetadata = metadata;
7382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
7482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
7582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @Override
7682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    public int getSize(@NonNull final Paint paint, final CharSequence text, final int start,
7782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int end, final Paint.FontMetricsInt fm) {
7882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        paint.getFontMetricsInt(mTmpFontMetrics);
7982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int fontHeight = Math.abs(mTmpFontMetrics.descent - mTmpFontMetrics.ascent);
8082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
8182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mRatio = fontHeight * 1.0f / mMetadata.getHeight();
8282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mHeight = (short) (mMetadata.getHeight() * mRatio);
8382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mWidth = (short) (mMetadata.getWidth() * mRatio);
8482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
8582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (fm != null) {
8682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            fm.ascent = mTmpFontMetrics.ascent;
8782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            fm.descent = mTmpFontMetrics.descent;
8882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            fm.top = mTmpFontMetrics.top;
8982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            fm.bottom = mTmpFontMetrics.bottom;
9082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
9182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
9282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return mWidth;
9382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
9482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
9582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
9682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
9782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
9882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
9982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    final EmojiMetadata getMetadata() {
10082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return mMetadata;
10182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
10282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
10382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
10482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return width of the span
10582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
10682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
10782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
10882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
10982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    final int getWidth() {
11082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return mWidth;
11182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
11282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
11382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
11482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return height of the span
11582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
11682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
11782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
11882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
11982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    final int getHeight() {
12082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return mHeight;
12182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
12282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
12382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
12482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
12582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
12682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
12782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    final float getRatio() {
12882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return mRatio;
12982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
13082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
13182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
13282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return unique id for the emoji that this EmojiSpan is used for
13382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
13482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @hide
13582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
13682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
13782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @VisibleForTesting
13882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    public final int getId() {
13982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return getMetadata().getId();
14082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
14182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir}
142