1 /* 2 * Copyright (C) 2018 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package leakcanary.internal 17 18 import android.content.Context 19 import android.graphics.Canvas 20 import android.graphics.Paint 21 import android.graphics.Path 22 import android.text.SpannableStringBuilder 23 import android.text.style.ReplacementSpan 24 import android.text.style.UnderlineSpan 25 import com.squareup.leakcanary.core.R 26 import kotlin.math.sin 27 import leakcanary.internal.navigation.getColorCompat 28 29 /** 30 * Inspired from https://github.com/flavienlaurent/spans and 31 * https://github.com/andyxialm/WavyLineView 32 */ 33 internal class SquigglySpan(context: Context) : ReplacementSpan() { 34 35 private val squigglyPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) 36 private val path: Path 37 private val referenceColor: Int 38 private val halfStrokeWidth: Float 39 private val amplitude: Float 40 private val halfWaveHeight: Float 41 private val periodDegrees: Float 42 43 private var width: Int = 0 44 45 init { 46 val resources = context.resources 47 squigglyPaint.style = Paint.Style.STROKE 48 squigglyPaint.color = context.getColorCompat(R.color.leak_canary_leak) 49 val strokeWidth = 50 resources.getDimensionPixelSize(R.dimen.leak_canary_squiggly_span_stroke_width) 51 .toFloat() 52 squigglyPaint.strokeWidth = strokeWidth 53 54 halfStrokeWidth = strokeWidth / 2 55 amplitude = resources.getDimensionPixelSize(R.dimen.leak_canary_squiggly_span_amplitude) 56 .toFloat() 57 periodDegrees = 58 resources.getDimensionPixelSize(R.dimen.leak_canary_squiggly_span_period_degrees) 59 .toFloat() 60 path = Path() 61 val waveHeight = 2 * amplitude + strokeWidth 62 halfWaveHeight = waveHeight / 2 63 referenceColor = context.getColorCompat(R.color.leak_canary_reference) 64 } 65 getSizenull66 override fun getSize( 67 paint: Paint, 68 text: CharSequence, 69 start: Int, 70 end: Int, 71 fm: Paint.FontMetricsInt? 72 ): Int { 73 width = paint.measureText(text, start, end) 74 .toInt() 75 return width 76 } 77 drawnull78 override fun draw( 79 canvas: Canvas, 80 text: CharSequence, 81 start: Int, 82 end: Int, 83 x: Float, 84 top: Int, 85 y: Int, 86 bottom: Int, 87 paint: Paint 88 ) { 89 squigglyHorizontalPath( 90 path, 91 x + halfStrokeWidth, 92 x + width - halfStrokeWidth, 93 bottom - halfWaveHeight, 94 amplitude, periodDegrees 95 ) 96 canvas.drawPath(path, squigglyPaint) 97 98 paint.color = referenceColor 99 canvas.drawText(text, start, end, x, y.toFloat(), paint) 100 } 101 102 companion object { 103 replaceUnderlineSpansnull104 fun replaceUnderlineSpans( 105 builder: SpannableStringBuilder, 106 context: Context 107 ) { 108 val underlineSpans = builder.getSpans(0, builder.length, UnderlineSpan::class.java) 109 for (span in underlineSpans) { 110 val start = builder.getSpanStart(span) 111 val end = builder.getSpanEnd(span) 112 builder.removeSpan(span) 113 builder.setSpan(SquigglySpan(context), start, end, 0) 114 } 115 } 116 117 @Suppress("LongParameterList") squigglyHorizontalPathnull118 private fun squigglyHorizontalPath( 119 path: Path, 120 left: Float, 121 right: Float, 122 centerY: Float, 123 amplitude: Float, 124 periodDegrees: Float 125 ) { 126 path.reset() 127 128 var y: Float 129 path.moveTo(left, centerY) 130 val period = (2 * Math.PI / periodDegrees).toFloat() 131 132 var x = 0f 133 while (x <= right - left) { 134 y = (amplitude * sin((40 + period * x).toDouble()) + centerY).toFloat() 135 path.lineTo(left + x, y) 136 x += 1f 137 } 138 } 139 } 140 } 141