Markwon/library/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpan.java
Dimitry 9251a608fe
Image sizes via HTML (#13)
* Image sizes specified in HTML
2017-11-11 15:54:52 +03:00

154 lines
4.7 KiB
Java

package ru.noties.markwon.spans;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.style.ReplacementSpan;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@SuppressWarnings("WeakerAccess")
public class AsyncDrawableSpan extends ReplacementSpan {
@IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER})
@Retention(RetentionPolicy.SOURCE)
@interface Alignment {
}
public static final int ALIGN_BOTTOM = 0;
public static final int ALIGN_BASELINE = 1;
public static final int ALIGN_CENTER = 2; // will only center if drawable height is less than text line height
private final SpannableTheme theme;
private final AsyncDrawable drawable;
private final int alignment;
private final boolean replacementTextIsLink;
public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) {
this(theme, drawable, ALIGN_BOTTOM);
}
public AsyncDrawableSpan(
@NonNull SpannableTheme theme,
@NonNull AsyncDrawable drawable,
@Alignment int alignment) {
this(theme, drawable, alignment, false);
}
public AsyncDrawableSpan(
@NonNull SpannableTheme theme,
@NonNull AsyncDrawable drawable,
@Alignment int alignment,
boolean replacementTextIsLink) {
this.theme = theme;
this.drawable = drawable;
this.alignment = alignment;
this.replacementTextIsLink = replacementTextIsLink;
// additionally set intrinsic bounds if empty
final Rect rect = drawable.getBounds();
if (rect.isEmpty()) {
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
}
}
@Override
public int getSize(
@NonNull Paint paint,
CharSequence text,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
@Nullable Paint.FontMetricsInt fm) {
// if we have no async drawable result - we will just render text
final int size;
if (drawable.hasResult()) {
final Rect rect = drawable.getBounds();
if (fm != null) {
fm.ascent = -rect.bottom;
fm.descent = 0;
fm.top = fm.ascent;
fm.bottom = 0;
}
size = rect.right;
} else {
// we will apply style here in case if theme modifies textSize or style (affects metrics)
if (replacementTextIsLink) {
theme.applyLinkStyle(paint);
}
// NB, no specific text handling (no new lines, etc)
size = (int) (paint.measureText(text, start, end) + .5F);
}
return size;
}
@Override
public void draw(
@NonNull Canvas canvas,
CharSequence text,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
float x,
int top,
int y,
int bottom,
@NonNull Paint paint) {
drawable.initWithKnownDimensions(canvas.getWidth(), paint.getTextSize());
final AsyncDrawable drawable = this.drawable;
if (drawable.hasResult()) {
final int b = bottom - drawable.getBounds().bottom;
final int save = canvas.save();
try {
final int translationY;
if (ALIGN_CENTER == alignment) {
translationY = b - ((bottom - top - drawable.getBounds().height()) / 2);
} else if (ALIGN_BASELINE == alignment) {
translationY = b - paint.getFontMetricsInt().descent;
} else {
translationY = b;
}
canvas.translate(x, translationY);
drawable.draw(canvas);
} finally {
canvas.restoreToCount(save);
}
} else {
// will it make sense to have additional background/borders for an image replacement?
// let's focus on main functionality and then think of it
final float textY = CanvasUtils.textCenterY(top, bottom, paint);
if (replacementTextIsLink) {
theme.applyLinkStyle(paint);
}
// NB, no specific text handling (no new lines, etc)
canvas.drawText(text, start, end, x, textY, paint);
}
}
public AsyncDrawable getDrawable() {
return drawable;
}
}