Last active
March 26, 2019 22:40
-
-
Save ephemient/8d069f469135825e454c34f0670420bc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.graphics.Canvas | |
import android.graphics.DashPathEffect | |
import android.graphics.Paint | |
import android.text.style.LineBackgroundSpan | |
import android.text.style.UnderlineSpan | |
/** Draws a dotted underline (as opposed to solid [UnderlineSpan]). */ | |
class DottedUnderlineSpan : LineBackgroundSpan { | |
private val paint = Paint() | |
private val dashIntervals = FloatArray(2) | |
override fun drawBackground( | |
c: Canvas, | |
p: Paint, | |
left: Int, | |
right: Int, | |
top: Int, | |
baseline: Int, | |
bottom: Int, | |
text: CharSequence, | |
start: Int, | |
end: Int, | |
lnum: Int | |
) { | |
dashIntervals[1] = 2 * p.underlineThickness | |
paint.apply { | |
set(p) | |
style = Paint.Style.STROKE | |
strokeWidth = p.underlineThickness | |
strokeCap = Paint.Cap.ROUND | |
pathEffect = DashPathEffect(dashIntervals, p.underlineThickness) | |
} | |
val underlineCenterY = baseline.toFloat() + p.underlinePosition + p.underlineThickness / 2 | |
c.drawLine(left.toFloat(), underlineCenterY, right.toFloat(), underlineCenterY, paint) | |
} | |
companion object { | |
// Copied from Skia's kStdUnderline_Offset/kStdUnderline_Thickness | |
private const val STDUNDERLINE_OFFSET = 1f / 9f | |
private const val STDUNDERLINE_THICKNESS = 1f / 18f | |
// [Paint.getUnderlinePosition()] is `@hide` :( | |
private val Paint.underlinePosition: Float | |
get() = textSize * STDUNDERLINE_OFFSET | |
// [Paint.getUnderlineThickness()] is `@hide` :( | |
private val Paint.underlineThickness: Float | |
get() = textSize * STDUNDERLINE_THICKNESS | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.graphics.Canvas | |
import android.graphics.Paint | |
import android.graphics.Rect | |
import android.text.style.LineBackgroundSpan | |
import android.text.style.StrikethroughSpan | |
import androidx.annotation.IntDef | |
/** Draws an oblique strikethrough (as opposed to horizontal [StrikethroughSpan]). */ | |
class ObliqueStrikethroughSpan( | |
@param:StrikethroughDirection private val strikethroughDirection: Int | |
) : LineBackgroundSpan { | |
private val rect = Rect() | |
private val paint = Paint() | |
@Retention(AnnotationRetention.SOURCE) | |
@IntDef(STRIKETHROUGH_DIRECTION_RISING, STRIKETHROUGH_DIRECTION_FALLING) | |
annotation class StrikethroughDirection | |
override fun drawBackground( | |
c: Canvas, | |
p: Paint, | |
left: Int, | |
right: Int, | |
top: Int, | |
baseline: Int, | |
bottom: Int, | |
text: CharSequence, | |
start: Int, | |
end: Int, | |
lnum: Int | |
) { | |
p.getTextBounds(text.toString(), start, end, rect) | |
paint.set(p) | |
paint.strokeWidth = p.textSize * STDUNDERLINE_THICKNESS | |
/* Suppose text="a". | |
* | |
* left right | |
* top ──╆━━━━━━━┪ | |
* ┃ ┃ ___, ┬── fm.ascent | |
* ┃ ┃ / | │ | |
* ┃ __, ┠── rect.top | | __, │ | |
* ┃ / | ┃ | | / | │ | |
* baseline ──┨ \_/|_/┃__ rect.bottom \__/\_/\_/|_/┼── baseline | |
* ┃ ┃ |\ │ | |
* ┃ ┃ |/ │__ fm.descent | |
* bottom ──┺┯━━━━━┯┛ | |
* rect.left rect.right | |
* | |
* We are drawing inside of Rect(left, top, right, bottom), | |
* this includes extra spacing and line height. | |
* | |
* We measure textBounds from the text region - although unstyled, | |
* due to the .toString() call. Hopefully that will be fine. | |
* | |
* This gives us tighter bounds than using FontMetrics, | |
* which does not take into account the current text. | |
* | |
* Keep in mind that the y coordinate grows downward. | |
* Upward measures (such as rect.top) are negative. | |
*/ | |
val bottomX = if (strikethroughDirection == STRIKETHROUGH_DIRECTION_FALLING) right else left | |
val bottomY = Math.min(bottom, baseline + rect.bottom) | |
val topX = if (strikethroughDirection == STRIKETHROUGH_DIRECTION_FALLING) left else right | |
val topY = Math.max(top, baseline + rect.top) | |
c.drawLine(bottomX.toFloat(), bottomY.toFloat(), topX.toFloat(), topY.toFloat(), paint) | |
} | |
companion object { | |
// This constant is copied from kStdUnderline_Thickness in Skia, | |
// which is how normal underlines and strikethroughs are drawn. | |
private const val STDUNDERLINE_THICKNESS = 1f / 18 | |
/** Strikethrough direction: bottom left to top right. */ | |
const val STRIKETHROUGH_DIRECTION_RISING = 0 | |
/** Strikethrough direction: bottom right to top left. */ | |
const val STRIKETHROUGH_DIRECTION_FALLING = 1 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment