• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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