parent
4b0c7789c2
commit
8e2b421b1f
@ -1,148 +0,0 @@ |
|||||||
/* Copyright 2017 Andrew Dawson |
|
||||||
* |
|
||||||
* This file is a part of Tusky. |
|
||||||
* |
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the |
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the |
|
||||||
* License, or (at your option) any later version. |
|
||||||
* |
|
||||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
|
||||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
||||||
* Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License along with Tusky; if not, |
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky.util; |
|
||||||
|
|
||||||
import android.graphics.Bitmap; |
|
||||||
import android.graphics.Canvas; |
|
||||||
import android.graphics.Paint; |
|
||||||
import android.graphics.drawable.BitmapDrawable; |
|
||||||
import android.graphics.drawable.Drawable; |
|
||||||
import android.text.SpannableStringBuilder; |
|
||||||
import android.text.Spanned; |
|
||||||
import android.text.SpannedString; |
|
||||||
import android.text.style.ReplacementSpan; |
|
||||||
import android.view.View; |
|
||||||
|
|
||||||
import com.bumptech.glide.Glide; |
|
||||||
import com.bumptech.glide.request.target.CustomTarget; |
|
||||||
import com.bumptech.glide.request.target.Target; |
|
||||||
import com.bumptech.glide.request.transition.Transition; |
|
||||||
import com.keylesspalace.tusky.entity.Emoji; |
|
||||||
|
|
||||||
import java.lang.ref.WeakReference; |
|
||||||
import java.util.List; |
|
||||||
import java.util.regex.Matcher; |
|
||||||
import java.util.regex.Pattern; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.annotation.Nullable; |
|
||||||
|
|
||||||
public class CustomEmojiHelper { |
|
||||||
|
|
||||||
/** |
|
||||||
* replaces emoji shortcodes in a text with EmojiSpans |
|
||||||
* @param text the text containing custom emojis |
|
||||||
* @param emojis a list of the custom emojis (nullable for backward compatibility with old mastodon instances) |
|
||||||
* @param view a reference to the a view the emojis will be shown in (should be the TextView, but parents of the TextView are also acceptable) |
|
||||||
* @return the text with the shortcodes replaced by EmojiSpans |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
public static Spanned emojifyText(@NonNull Spanned text, @Nullable List<Emoji> emojis, @NonNull final View view) { |
|
||||||
|
|
||||||
if (emojis != null && !emojis.isEmpty()) { |
|
||||||
|
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder(text); |
|
||||||
for (Emoji emoji : emojis) { |
|
||||||
CharSequence pattern = new StringBuilder(":").append(emoji.getShortcode()).append(':'); |
|
||||||
Matcher matcher = Pattern.compile(pattern.toString(), Pattern.LITERAL) |
|
||||||
.matcher(text); |
|
||||||
while (matcher.find()) { |
|
||||||
EmojiSpan span = new EmojiSpan(view); |
|
||||||
builder.setSpan(span, matcher.start(), matcher.end(), 0); |
|
||||||
Glide.with(view) |
|
||||||
.asBitmap() |
|
||||||
.load(emoji.getUrl()) |
|
||||||
.into(span.getTarget()); |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return builder; |
|
||||||
} |
|
||||||
|
|
||||||
return text; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
public static Spanned emojifyString(@NonNull String string, @Nullable List<Emoji> emojis, @NonNull final View ciew) { |
|
||||||
return emojifyText(new SpannedString(string), emojis, ciew); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public static class EmojiSpan extends ReplacementSpan { |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private Drawable imageDrawable; |
|
||||||
private WeakReference<View> viewWeakReference; |
|
||||||
|
|
||||||
EmojiSpan(View view) { |
|
||||||
this.viewWeakReference = new WeakReference<>(view); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, |
|
||||||
@Nullable Paint.FontMetricsInt fm) { |
|
||||||
|
|
||||||
/* update FontMetricsInt or otherwise span does not get drawn when |
|
||||||
it covers the whole text */ |
|
||||||
Paint.FontMetricsInt metrics = paint.getFontMetricsInt(); |
|
||||||
if (fm != null) { |
|
||||||
fm.top = (int)((float)metrics.top * 1.3); |
|
||||||
fm.ascent = (int)((float)metrics.ascent * 1.3); |
|
||||||
fm.descent = (int)((float)metrics.descent * 2.0); |
|
||||||
fm.bottom = (int)((float)metrics.bottom * 3.5); |
|
||||||
// fm.leading = (int)((float)metrics.leading); // useless to change
|
|
||||||
} |
|
||||||
|
|
||||||
return (int) (paint.getTextSize()*2.0); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, |
|
||||||
int top, int y, int bottom, @NonNull Paint paint) { |
|
||||||
if (imageDrawable == null) return; |
|
||||||
canvas.save(); |
|
||||||
|
|
||||||
int emojiSize = (int) (paint.getTextSize() * 2.0); |
|
||||||
imageDrawable.setBounds(0, 0, emojiSize, emojiSize); |
|
||||||
|
|
||||||
int transY = bottom - imageDrawable.getBounds().bottom; |
|
||||||
transY -= paint.getFontMetricsInt().descent/2; |
|
||||||
canvas.translate(x, transY); |
|
||||||
imageDrawable.draw(canvas); |
|
||||||
canvas.restore(); |
|
||||||
} |
|
||||||
|
|
||||||
Target<Bitmap> getTarget(){ |
|
||||||
return new CustomTarget<Bitmap>() { |
|
||||||
@Override |
|
||||||
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { |
|
||||||
View view = viewWeakReference.get(); |
|
||||||
if (view != null) { |
|
||||||
imageDrawable = new BitmapDrawable(view.getContext().getResources(), resource); |
|
||||||
view.invalidate(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onLoadCleared(@Nullable Drawable placeholder) { |
|
||||||
//Do nothing on load cleared
|
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,120 @@ |
|||||||
|
/* Copyright 2017 Andrew Dawson |
||||||
|
* |
||||||
|
* This file is a part of Tusky. |
||||||
|
* |
||||||
|
* This program is free software; you can redistribute it and/or modify it under the terms of the |
||||||
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
||||||
|
* Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky; if not, |
||||||
|
* see <http://www.gnu.org/licenses>. */ |
||||||
|
|
||||||
|
package com.keylesspalace.tusky.util |
||||||
|
|
||||||
|
import android.graphics.Bitmap |
||||||
|
import android.graphics.Canvas |
||||||
|
import android.graphics.Paint |
||||||
|
import android.graphics.drawable.BitmapDrawable |
||||||
|
import android.graphics.drawable.Drawable |
||||||
|
import android.text.SpannableStringBuilder |
||||||
|
import android.text.Spanned |
||||||
|
import android.text.SpannedString |
||||||
|
import android.text.style.ReplacementSpan |
||||||
|
import android.view.View |
||||||
|
|
||||||
|
import com.bumptech.glide.Glide |
||||||
|
import com.bumptech.glide.request.target.CustomTarget |
||||||
|
import com.bumptech.glide.request.target.Target |
||||||
|
import com.bumptech.glide.request.transition.Transition |
||||||
|
import com.keylesspalace.tusky.entity.Emoji |
||||||
|
|
||||||
|
import java.lang.ref.WeakReference |
||||||
|
import java.util.regex.Pattern |
||||||
|
import androidx.preference.PreferenceManager |
||||||
|
|
||||||
|
/** |
||||||
|
* replaces emoji shortcodes in a text with EmojiSpans |
||||||
|
* @param text the text containing custom emojis |
||||||
|
* @param emojis a list of the custom emojis (nullable for backward compatibility with old mastodon instances) |
||||||
|
* @param view a reference to the a view the emojis will be shown in (should be the TextView, but parents of the TextView are also acceptable) |
||||||
|
* @return the text with the shortcodes replaced by EmojiSpans |
||||||
|
*/ |
||||||
|
fun emojifyText(text: Spanned, emojis: List<Emoji>?, view: View) : Spanned { |
||||||
|
if (emojis != null && emojis.isNotEmpty()) { |
||||||
|
val builder = SpannableStringBuilder(text) |
||||||
|
for (emoji in emojis) { |
||||||
|
val pattern = StringBuilder(":").append(emoji.shortcode).append(":") |
||||||
|
val matcher = Pattern.compile(pattern.toString(), Pattern.LITERAL) |
||||||
|
.matcher(text) |
||||||
|
while(matcher.find()) { |
||||||
|
val span = EmojiSpan(WeakReference<View>(view)) |
||||||
|
builder.setSpan(span, matcher.start(), matcher.end(), 0); |
||||||
|
Glide.with(view) |
||||||
|
.asBitmap() |
||||||
|
.load(emoji.url) |
||||||
|
.into(span.getTarget()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return builder |
||||||
|
} |
||||||
|
|
||||||
|
return text |
||||||
|
} |
||||||
|
|
||||||
|
fun emojifyString(string: String, emojis: List<Emoji>?, view: View) : Spanned { |
||||||
|
return emojifyText(SpannedString(string), emojis, view) |
||||||
|
} |
||||||
|
|
||||||
|
public class EmojiSpan(val viewWeakReference: WeakReference<View>) : ReplacementSpan() { |
||||||
|
var imageDrawable: Drawable? = null |
||||||
|
|
||||||
|
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?) : Int { |
||||||
|
if (fm != null) { |
||||||
|
/* update FontMetricsInt or otherwise span does not get drawn when |
||||||
|
* it covers the whole text */ |
||||||
|
val metrics = paint.fontMetricsInt |
||||||
|
fm.top = (metrics.top * 1.3f).toInt() |
||||||
|
fm.ascent = (metrics.ascent * 1.3f).toInt() |
||||||
|
fm.descent = (metrics.descent * 2.0f).toInt() |
||||||
|
fm.bottom = (metrics.bottom * 3.5f).toInt() |
||||||
|
} |
||||||
|
|
||||||
|
return (paint.textSize * 2.0).toInt() |
||||||
|
} |
||||||
|
|
||||||
|
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) { |
||||||
|
if (imageDrawable == null) |
||||||
|
return |
||||||
|
|
||||||
|
canvas.save() |
||||||
|
|
||||||
|
val emojiSize = getSize(paint, text, start, end, null) |
||||||
|
imageDrawable!!.setBounds(0, 0, emojiSize, emojiSize) |
||||||
|
|
||||||
|
var transY = bottom - imageDrawable!!.bounds.bottom |
||||||
|
transY -= paint.fontMetricsInt.descent / 2; |
||||||
|
|
||||||
|
canvas.translate(x, transY.toFloat()) |
||||||
|
imageDrawable!!.draw(canvas) |
||||||
|
canvas.restore() |
||||||
|
} |
||||||
|
|
||||||
|
fun getTarget(): Target<Bitmap> { |
||||||
|
return object : CustomTarget<Bitmap>() { |
||||||
|
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) { |
||||||
|
val view = viewWeakReference.get() |
||||||
|
if (view != null) { |
||||||
|
imageDrawable = BitmapDrawable(view.context.resources, resource) |
||||||
|
view.invalidate() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onLoadCleared(placeholder: Drawable?) {} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue