main

square/leakcanary

Last updated at: 29/12/2023 09:37

SquigglySpan.kt

TLDR

The provided file SquigglySpan.kt is a Kotlin file in the leakcanary.internal package. It contains the implementation of the SquigglySpan class, which is a custom span that can be used with SpannableStringBuilder to replace UnderlineSpan with a squiggly line. The class also includes the necessary methods to draw the squiggly line on a Canvas. The file also includes a companion object that provides a method to replace UnderlineSpan instances in a SpannableStringBuilder with instances of SquigglySpan.

Classes

SquigglySpan

The SquigglySpan class is a custom ReplacementSpan that can be used to replace UnderlineSpan in a SpannableStringBuilder. It draws a squiggly line under the text it spans.

Methods

getSize

This method measures the text length and returns its size.

draw

This method draws the squiggly line on a Canvas.

Companion Object

replaceUnderlineSpans

This method replaces UnderlineSpan with instances of SquigglySpan in a SpannableStringBuilder.

squigglyHorizontalPath

This method constructs the path of the squiggly line.

/*
 * Copyright (C) 2018 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package leakcanary.internal

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.text.SpannableStringBuilder
import android.text.style.ReplacementSpan
import android.text.style.UnderlineSpan
import com.squareup.leakcanary.core.R
import kotlin.math.sin
import leakcanary.internal.navigation.getColorCompat

/**
 * Inspired from https://github.com/flavienlaurent/spans and
 * https://github.com/andyxialm/WavyLineView
 */
internal class SquigglySpan(context: Context) : ReplacementSpan() {

  private val squigglyPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
  private val path: Path
  private val referenceColor: Int
  private val halfStrokeWidth: Float
  private val amplitude: Float
  private val halfWaveHeight: Float
  private val periodDegrees: Float

  private var width: Int = 0

  init {
    val resources = context.resources
    squigglyPaint.style = Paint.Style.STROKE
    squigglyPaint.color = context.getColorCompat(R.color.leak_canary_leak)
    val strokeWidth =
      resources.getDimensionPixelSize(R.dimen.leak_canary_squiggly_span_stroke_width)
        .toFloat()
    squigglyPaint.strokeWidth = strokeWidth

    halfStrokeWidth = strokeWidth / 2
    amplitude = resources.getDimensionPixelSize(R.dimen.leak_canary_squiggly_span_amplitude)
      .toFloat()
    periodDegrees =
      resources.getDimensionPixelSize(R.dimen.leak_canary_squiggly_span_period_degrees)
        .toFloat()
    path = Path()
    val waveHeight = 2 * amplitude + strokeWidth
    halfWaveHeight = waveHeight / 2
    referenceColor = context.getColorCompat(R.color.leak_canary_reference)
  }

  override fun getSize(
    paint: Paint,
    text: CharSequence,
    start: Int,
    end: Int,
    fm: Paint.FontMetricsInt?
  ): Int {
    width = paint.measureText(text, start, end)
      .toInt()
    return width
  }

  override fun draw(
    canvas: Canvas,
    text: CharSequence,
    start: Int,
    end: Int,
    x: Float,
    top: Int,
    y: Int,
    bottom: Int,
    paint: Paint
  ) {
    squigglyHorizontalPath(
      path,
      x + halfStrokeWidth,
      x + width - halfStrokeWidth,
      bottom - halfWaveHeight,
      amplitude, periodDegrees
    )
    canvas.drawPath(path, squigglyPaint)

    paint.color = referenceColor
    canvas.drawText(text, start, end, x, y.toFloat(), paint)
  }

  companion object {

    fun replaceUnderlineSpans(
      builder: SpannableStringBuilder,
      context: Context
    ) {
      val underlineSpans = builder.getSpans(0, builder.length, UnderlineSpan::class.java)
      for (span in underlineSpans) {
        val start = builder.getSpanStart(span)
        val end = builder.getSpanEnd(span)
        builder.removeSpan(span)
        builder.setSpan(SquigglySpan(context), start, end, 0)
      }
    }

    @Suppress("LongParameterList")
    private fun squigglyHorizontalPath(
      path: Path,
      left: Float,
      right: Float,
      centerY: Float,
      amplitude: Float,
      periodDegrees: Float
    ) {
      path.reset()

      var y: Float
      path.moveTo(left, centerY)
      val period = (2 * Math.PI / periodDegrees).toFloat()

      var x = 0f
      while (x <= right - left) {
        y = (amplitude * sin((40 + period * x).toDouble()) + centerY).toFloat()
        path.lineTo(left + x, y)
        x += 1f
      }
    }
  }
}