package ru.noties.markwon.spans2; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.style.ReplacementSpan; import ru.noties.debug.Debug; // we will use Replacement span because code blocks cannot contain other markdown // so we will render the string (not a charSequence with possible metric affecting spans) public class CodeSpan extends ReplacementSpan/* implements LeadingMarginSpan*/ { private final boolean multiline; private final int start; private final int end; private final Rect rect = new Rect(); private final Rect borderRect = new Rect(); private final Paint paint = new Paint(); public CodeSpan(boolean multiline, int start, int end) { this.multiline = multiline; this.start = start; this.end = end; paint.setStyle(Paint.Style.FILL); } @Override public int getSize( @NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm ) { final CharSequence cs = text.subSequence(start, end); final int width = 32 + (int) (paint.measureText(cs, 0, cs.length()) + .5F); // final StaticLayout layout = new StaticLayout(cs, new TextPaint(paint), 10000, Layout.Alignment.ALIGN_NORMAL, 1.F, .0F, false); // final float width = layout.getLineWidth(0); // final int out = 32 + (int) (width + .5F); // Debug.i("text: %s, width: %s", cs, width); if (fm != null) { // we add a padding top & bottom Debug.i("a: %s, d: %s, t: %s, b: %s", fm.ascent, fm.descent, fm.top, fm.bottom); final float ratio = .62F; // golden ratio fm.ascent = fm.ascent - 8; fm.descent = (int) (-fm.ascent * ratio); fm.top = fm.ascent; fm.bottom = fm.descent; } return width; } @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 ) { final int left = (int) (x + .5F); final int right; if (multiline) { right = canvas.getWidth(); } else { final int width = (16 * 2) + (int) (paint.measureText(text, start, end) + .5F); right = left + width; } rect.set(left, top, right, bottom); // okay, draw background first drawBackground(canvas); // then, if any, draw borders drawBorders(canvas, this.start == start, this.end == end); // draw text // y center position final int b = bottom - ((bottom - top) / 2) - (int) ((paint.descent() + paint.ascent()) / 2); // if (config.textColor != 0) { // // we will use Paint object that is used to draw all the text (textSize, textColor, typeface, etc) // paint.setColor(config.textColor); // } canvas.drawText(text, start, end, x + 16, b, paint); // Debug.i("text: %s, x: %s, top: %s, y: %s, bottom: %s", text.subSequence(start, end), x, top, y, bottom); // // final CharSequence cs = text.subSequence(start, end); // // final int width = 32 + (int) (paint.measureText(cs, 0, cs.length()) + .5F); // // final int left = (int) (x + .5F); // final int right = multiline // ? canvas.getWidth() // : left + width; // // final Rect rect = new Rect( // left, // top, // right, // bottom // ); // // final Paint p = new Paint(); // p.setStyle(Paint.Style.FILL); // p.setColor(0x80ff0000); // canvas.drawRect(rect, p); // // // y center position // final int b = bottom - ((bottom - top) / 2) - (int) ((paint.descent() + paint.ascent()) / 2); // p.setColor(0xFF000000); // canvas.drawText(cs, 0, cs.length(), x + 16, b, paint); } private void drawBackground(Canvas canvas) { // final int color = config.backgroundColor; // if (color != 0) { paint.setColor(0x40ff0000); canvas.drawRect(rect, paint); // } } private void drawBorders(Canvas canvas, boolean top, boolean bottom) { final int color = 0xFFff0000; final int width = 4; // if (color == 0 // || width == 0) { // return; // } paint.setColor(color); // left and right are always drawn // LEFT borderRect.set(rect.left, rect.top, rect.left + width, rect.bottom); canvas.drawRect(borderRect, paint); // RIGHT borderRect.set(rect.right - width, rect.top, rect.right, rect.bottom); canvas.drawRect(borderRect, paint); // TOP if (top) { borderRect.set(rect.left, rect.top, rect.right, rect.top + width); canvas.drawRect(borderRect, paint); } // BOTTOM if (bottom) { borderRect.set(rect.left, rect.bottom - width, rect.right, rect.bottom); canvas.drawRect(borderRect, paint); } } // @Override // public int getLeadingMargin(boolean first) { // return 1; // } // // @Override // public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { //// Debug.i("x: %d, top: %d, bottom: %d", x, top, bottom); // //// Debug.i("this: [%d, %d], came: [%d, %d]", this.start, this.end, start, end); // Debug.i("x: %d, canvas: [%d-%d], text: %s", x, c.getWidth(), c.getHeight(), (text.subSequence(start, end))); // // // the thing is... if we do not draw, then text won't be drawn also // final Rect rect = new Rect(); // // final Paint paint = new Paint(); // paint.setStyle(Paint.Style.FILL); // paint.setColor(0xffcccccc); // // rect.set(x, top, c.getWidth(), bottom); // c.drawRect(rect, paint); // // if (this.start == start) { // this.top = top; // //// final int save = c.save(); //// try { //// c.drawColor(0x00ffffff); //// } finally { //// c.restoreToCount(save); //// } // //// c.drawColor(0x00ffffff); // } // // if (this.end == end) { // // draw borders // final Rect r = new Rect(x + 1, this.top, c.getWidth() - x, bottom); // final Paint pa = new Paint(); // pa.setStyle(Paint.Style.STROKE); // pa.setColor(0xff999999); // c.drawRect(r, pa); // } //// rect.inset((int) paint.getStrokeWidth(), (int) paint.getStrokeWidth()); //// paint.setStyle(Paint.Style.STROKE); //// paint.setColor(0xff333333); //// c.drawRect(rect, paint); // } // @Override // public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) { //// int ht = mDrawable.getIntrinsicHeight(); //// //// int need = ht - (v + fm.descent - fm.ascent - istartv); //// if (need > 0) //// fm.descent += need; //// //// need = ht - (v + fm.bottom - fm.top - istartv); //// if (need > 0) //// fm.bottom += need; //// // //// final int lineOffset = v - spanstartv; //// final int desired = 128; //// final int currentLineHeight = -fm.ascent + fm.descent; //// final float ratio = (float) desired / currentLineHeight; //// //// Debug.i("fm, came: %s", fm); //// Debug.i("lineOffset: %d, current: %d, ratio: %s", lineOffset, currentLineHeight, ratio); //// //// fm.ascent = (int) (ratio * fm.ascent + .5F); //// fm.descent = (int) (ratio * fm.descent + .5F); //// //// Debug.i("fm, out: %s", fm); // //// Debug.i("top: %d, bottom: %d, ascent: %d, descent: %d", fm.top, fm.bottom, fm.ascent, fm.descent); //// Debug.i("lineHeight: %d, v: %d, spanstartv: %d", lineOffset, v, spanstartv); //// //// final int h = 128; //// final int descentNeed = h - (v + fm.descent - fm.ascent - spanstartv); //// if (descentNeed > 0) { //// fm.ascent -= descentNeed / 2; //// fm.descent += descentNeed / 2; //// } //// final int bottomNeed = h - (v + fm.bottom - fm.top - spanstartv); //// if (bottomNeed > 0) { //// fm.top -= bottomNeed; //// fm.bottom += bottomNeed; //// } //// //// Debug.i("out, ascent: %d, descent: %d, bottom: %d", fm.ascent, fm.descent, fm.bottom); // } }