Zaimanhua: Improve comment rendering (#10340)

- Better text layout and spacing for comments.
- Ensure the generated image is not too short, to avoid being displayed as a double-page spread.
This commit is contained in:
zhongfly 2025-09-01 17:09:29 +08:00 committed by Draff
parent 314b8f3848
commit 14b5edc771
Signed by: Draff
GPG Key ID: E8A89F3211677653
2 changed files with 76 additions and 31 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'Zaimanhua'
extClass = '.Zaimanhua'
extVersionCode = 11
extVersionCode = 12
isNsfw = false
}

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.zh.zaimanhua
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Typeface
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
@ -13,21 +14,26 @@ import okhttp3.Response
import okhttp3.ResponseBody.Companion.asResponseBody
import okio.Buffer
fun parseChapterComments(response: Response, count: Int): List<String> {
fun parseChapterComments(response: Response): List<String> {
val result = response.parseAs<ResponseDto<CommentDataDto>>()
val comments = result.data.toCommentList()
return if (result.errmsg.isNotBlank()) {
throw Exception(result.errmsg)
} else {
comments.take(count)
comments
}
}
object CommentsInterceptor : Interceptor {
private const val MAX_HEIGHT = 1920
private const val WIDTH = 1080
private const val UNIT = 32
private const val UNIT_F = UNIT.toFloat()
private const val X_PADDING: Float = 50f
private const val Y_PADDING: Float = 25f
private const val SPACING_MULT: Float = 1f
private const val SPACING_ADD: Float = 0f
private const val HEADING_FONT_SIZE: Float = 36f
private const val BODY_FONT_SIZE: Float = 30f
private const val SPACING: Float = BODY_FONT_SIZE * SPACING_MULT + SPACING_ADD
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
@ -35,46 +41,85 @@ object CommentsInterceptor : Interceptor {
if (request.tag(String::class) != COMMENTS_FLAG) {
return response
}
val comments = parseChapterComments(response, MAX_HEIGHT / (UNIT * 2) - 1).toMutableList()
comments.add(0, "章末吐槽:")
val paint = TextPaint().apply {
val paintHeading = TextPaint().apply {
color = Color.BLACK
textSize = UNIT_F
textSize = HEADING_FONT_SIZE
typeface = Typeface.DEFAULT_BOLD
isAntiAlias = true
}
var height = UNIT
val layouts = comments.map {
@Suppress("DEPRECATION")
val heading = StaticLayout(
"章末吐槽:",
paintHeading,
(WIDTH - 2 * X_PADDING).toInt(),
Layout.Alignment.ALIGN_NORMAL,
SPACING_MULT,
SPACING_ADD,
true,
)
val comments = parseChapterComments(response).toMutableList()
val paintBody = TextPaint().apply {
color = Color.BLACK
textSize = BODY_FONT_SIZE
typeface = Typeface.DEFAULT
isAntiAlias = true
}
var currentHeight = Y_PADDING + heading.height
val bodyLayouts = mutableListOf<StaticLayout>()
for (comment in comments) {
@Suppress("DEPRECATION")
StaticLayout(it, paint, WIDTH - 2 * UNIT, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false)
}.takeWhile {
val lineHeight = it.height + UNIT
if (height + lineHeight <= MAX_HEIGHT) {
height += lineHeight
true
} else {
false
val layout = StaticLayout(
comment,
paintBody,
(WIDTH - 2 * X_PADDING).toInt(),
Layout.Alignment.ALIGN_NORMAL,
SPACING_MULT,
SPACING_ADD,
true,
)
val lineHeight = SPACING + layout.height
// If adding this comment doesn't exceed the max height, add it.
// If it does exceed and it's a single line, stop adding more comments.
// Otherwise, if it's multi-line, just skip it and try the next comment.
if (currentHeight + lineHeight <= MAX_HEIGHT) {
bodyLayouts.add(layout)
currentHeight += lineHeight
} else if (layout.lineCount == 1) {
break
}
}
val bitmap = Bitmap.createBitmap(WIDTH, height, Bitmap.Config.ARGB_8888)
bitmap.eraseColor(Color.WHITE)
val canvas = Canvas(bitmap)
// The bitmap height must be no more than MAX_HEIGHT
// and no less than its width to prevent automatic double-page splitting.
val bitmapHeight = (currentHeight + Y_PADDING).toInt().coerceIn(WIDTH, MAX_HEIGHT)
var y = UNIT
for (layout in layouts) {
canvas.save()
canvas.translate(UNIT_F, y.toFloat())
layout.draw(canvas)
canvas.restore()
y += layout.height + UNIT
val bitmap = Bitmap.createBitmap(WIDTH, bitmapHeight, Bitmap.Config.ARGB_8888)
Canvas(bitmap).apply {
drawColor(Color.WHITE)
heading.draw(this, X_PADDING, Y_PADDING)
var y = Y_PADDING + heading.height + SPACING
for (layout in bodyLayouts) {
layout.draw(this, X_PADDING, y)
y += layout.height + SPACING
}
}
val responseBody = Buffer().run {
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream())
asResponseBody("image/jpeg".toMediaType())
bitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream())
asResponseBody("image/png".toMediaType())
}
return response.newBuilder().body(responseBody).build()
}
@Suppress("SameParameterValue")
private fun StaticLayout.draw(canvas: Canvas, x: Float, y: Float) {
canvas.save()
canvas.translate(x, y)
this.draw(canvas)
canvas.restore()
}
}