AssimilateTranslate/app/src/main/java/com/assimilate/alltrans/common/TextGraphic.kt
2024-08-16 18:29:50 +08:00

226 lines
7.2 KiB
Kotlin

package com.assimilate.alltrans.common
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Typeface
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.util.Log
import com.assimilate.alltrans.MyApp
import com.assimilate.alltrans.curview.GraphicOverlay
import com.google.mlkit.vision.text.Text
import kotlin.math.max
import kotlin.math.min
class TextGraphic(
overlay: GraphicOverlay?,
private val text: Text,
private val textShow: Boolean,
private val needTrans: Boolean,
private val fbFrom: String
) : GraphicOverlay.Graphic(overlay) {
private val shouldGroupRecognizedTextInBlocks: Boolean =
PreferenceUtils.shouldGroupRecognizedTextInBlocks(MyApp.applicationContext())
private val showLanguageTag: Boolean =
PreferenceUtils.showLanguageTag(MyApp.applicationContext())
private val showConfidence: Boolean =
PreferenceUtils.shouldShowTextConfidence(MyApp.applicationContext())
private val textPaint: TextPaint = TextPaint().apply {
color = TEXT_COLOR
textSize = TEXT_SIZE
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
isFakeBoldText = true
}
private val labelPaint: Paint = Paint().apply {
color = MARKER_COLOR
style = Paint.Style.FILL
}
private var hiddenTextBlocks: MutableList<Boolean> =
MutableList(text.textBlocks.size) { !textShow }
private var isVisible: Boolean
private var isLoading: Boolean = false
// 翻译后的文本
private var translatedTextBlocks: List<String> = listOf()
init {
isVisible = textShow
if (needTrans) {
isLoading = true
TranslationManager(text, fbFrom) { translatedTextPairs ->
translatedTextBlocks = translatedTextPairs.map { it.first }
isLoading = false
// 可以同时打印原Text和翻译后的结果
translatedTextPairs.forEach { (translated, original) ->
Log.d("Translation", "Original: $original -> Translated: $translated")
}
postInvalidate()
}
}
// Redraw the overlay, as this graphic has been added.
postInvalidate()
}
override fun draw(canvas: Canvas) {
if (!isVisible) return
for ((index, textBlock) in text.textBlocks.withIndex()) {
if (hiddenTextBlocks[index]) continue
drawTextBlock(textBlock, canvas, index)
}
}
private fun drawLoadingIndicator(textBlock: Text.TextBlock, canvas: Canvas) {
val rect = RectF(textBlock.boundingBox)
val centerX = (rect.left + rect.right) / 2
val centerY = (rect.top + rect.bottom) / 2
val radius = min(rect.width(), rect.height()) / 4
val paint = Paint().apply {
color = Color.BLUE
style = Paint.Style.STROKE
strokeWidth = 8f
}
canvas.drawCircle(centerX, centerY, radius, paint)
}
private fun drawTextBlock(textBlock: Text.TextBlock, canvas: Canvas, index: Int) {
val translatedBlockText = translatedTextBlocks.getOrNull(index) ?: textBlock.text
if (shouldGroupRecognizedTextInBlocks) {
val rect = RectF(textBlock.boundingBox)
drawText(
getFormattedText(translatedBlockText, textBlock.recognizedLanguage, null),
rect,
canvas,
textPaint
)
}
// if (isLoading) {
// drawLoadingIndicator(textBlock, canvas)
// }
}
override fun contains(x: Float, y: Float): Boolean {
for (textBlock in text.textBlocks) {
val rect = RectF(textBlock.boundingBox)
val x0 = translateX(rect.left)
val x1 = translateX(rect.right)
rect.left = min(x0, x1)
rect.right = max(x0, x1)
rect.top = translateY(rect.top)
rect.bottom = translateY(rect.bottom)
if (rect.contains(x, y)) {
return true
}
}
return false
}
fun showTextBlockAt(x: Float, y: Float) {
isVisible = true
for ((index, textBlock) in text.textBlocks.withIndex()) {
val rect = RectF(textBlock.boundingBox)
val x0 = translateX(rect.left)
val x1 = translateX(rect.right)
rect.left = min(x0, x1)
rect.right = max(x0, x1)
rect.top = translateY(rect.top)
rect.bottom = translateY(rect.bottom)
if (rect.contains(x, y)) {
hiddenTextBlocks[index] = false
postInvalidate()
break
}
}
}
fun hideTextBlock() {
isVisible = false
for (i in hiddenTextBlocks.indices) {
hiddenTextBlocks[i] = true
}
postInvalidate()
}
private fun getFormattedText(text: String, languageTag: String, confidence: Float?): String {
val res = if (showLanguageTag) String.format(
TEXT_WITH_LANGUAGE_TAG_FORMAT,
languageTag,
text
) else text
return if (showConfidence && confidence != null) String.format(
"%s (%.2f)",
res,
confidence
) else res
}
private fun drawText(text: String, rect: RectF, canvas: Canvas, paint: TextPaint) {
val x0 = translateX(rect.left)
val x1 = translateX(rect.right)
rect.left = min(x0, x1)
rect.right = max(x0, x1)
rect.top = translateY(rect.top)
rect.bottom = translateY(rect.bottom)
// 绘制背景矩形
canvas.drawRect(rect, labelPaint)
// 准备文本绘制
val textPaintCopy = TextPaint(paint)
val availableWidth = rect.width().toInt()
val availableHeight = rect.height().toInt()
var textSize = textPaintCopy.textSize
var textLayout: StaticLayout
while (textSize > 0) {
textPaintCopy.textSize = textSize
textLayout =
StaticLayout.Builder.obtain(text, 0, text.length, textPaintCopy, availableWidth)
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.setLineSpacing(0.0f, 1.0f)
.setIncludePad(false)
.build()
if (textLayout.height <= availableHeight) {
break
}
textSize -= 1
}
textLayout =
StaticLayout.Builder.obtain(text, 0, text.length, textPaintCopy, availableWidth)
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.setLineSpacing(0.0f, 1.0f)
.setIncludePad(false)
.build()
canvas.save()
canvas.translate(rect.left, rect.top)
textLayout.draw(canvas)
canvas.restore()
}
companion object {
private const val TAG = "TextGraphic"
private const val TEXT_WITH_LANGUAGE_TAG_FORMAT = "%s:%s"
private val TEXT_COLOR = Color.parseColor("#FF474747")
private val MARKER_COLOR = Color.parseColor("#FFD9D9D9")
private const val TEXT_SIZE = 44.0f
}
}