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 = MutableList(text.textBlocks.size) { !textShow } private var isVisible: Boolean private var isLoading: Boolean = false // 翻译后的文本 private var translatedTextBlocks: List = 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 } }