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:
parent
314b8f3848
commit
14b5edc771
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Zaimanhua'
|
extName = 'Zaimanhua'
|
||||||
extClass = '.Zaimanhua'
|
extClass = '.Zaimanhua'
|
||||||
extVersionCode = 11
|
extVersionCode = 12
|
||||||
isNsfw = false
|
isNsfw = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.zh.zaimanhua
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.text.Layout
|
import android.text.Layout
|
||||||
import android.text.StaticLayout
|
import android.text.StaticLayout
|
||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
@ -13,21 +14,26 @@ import okhttp3.Response
|
|||||||
import okhttp3.ResponseBody.Companion.asResponseBody
|
import okhttp3.ResponseBody.Companion.asResponseBody
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
|
|
||||||
fun parseChapterComments(response: Response, count: Int): List<String> {
|
fun parseChapterComments(response: Response): List<String> {
|
||||||
val result = response.parseAs<ResponseDto<CommentDataDto>>()
|
val result = response.parseAs<ResponseDto<CommentDataDto>>()
|
||||||
val comments = result.data.toCommentList()
|
val comments = result.data.toCommentList()
|
||||||
return if (result.errmsg.isNotBlank()) {
|
return if (result.errmsg.isNotBlank()) {
|
||||||
throw Exception(result.errmsg)
|
throw Exception(result.errmsg)
|
||||||
} else {
|
} else {
|
||||||
comments.take(count)
|
comments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object CommentsInterceptor : Interceptor {
|
object CommentsInterceptor : Interceptor {
|
||||||
private const val MAX_HEIGHT = 1920
|
private const val MAX_HEIGHT = 1920
|
||||||
private const val WIDTH = 1080
|
private const val WIDTH = 1080
|
||||||
private const val UNIT = 32
|
private const val X_PADDING: Float = 50f
|
||||||
private const val UNIT_F = UNIT.toFloat()
|
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 {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
@ -35,46 +41,85 @@ object CommentsInterceptor : Interceptor {
|
|||||||
if (request.tag(String::class) != COMMENTS_FLAG) {
|
if (request.tag(String::class) != COMMENTS_FLAG) {
|
||||||
return response
|
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
|
color = Color.BLACK
|
||||||
textSize = UNIT_F
|
textSize = HEADING_FONT_SIZE
|
||||||
|
typeface = Typeface.DEFAULT_BOLD
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var height = UNIT
|
@Suppress("DEPRECATION")
|
||||||
val layouts = comments.map {
|
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")
|
@Suppress("DEPRECATION")
|
||||||
StaticLayout(it, paint, WIDTH - 2 * UNIT, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false)
|
val layout = StaticLayout(
|
||||||
}.takeWhile {
|
comment,
|
||||||
val lineHeight = it.height + UNIT
|
paintBody,
|
||||||
if (height + lineHeight <= MAX_HEIGHT) {
|
(WIDTH - 2 * X_PADDING).toInt(),
|
||||||
height += lineHeight
|
Layout.Alignment.ALIGN_NORMAL,
|
||||||
true
|
SPACING_MULT,
|
||||||
} else {
|
SPACING_ADD,
|
||||||
false
|
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)
|
// The bitmap height must be no more than MAX_HEIGHT
|
||||||
bitmap.eraseColor(Color.WHITE)
|
// and no less than its width to prevent automatic double-page splitting.
|
||||||
val canvas = Canvas(bitmap)
|
val bitmapHeight = (currentHeight + Y_PADDING).toInt().coerceIn(WIDTH, MAX_HEIGHT)
|
||||||
|
|
||||||
var y = UNIT
|
val bitmap = Bitmap.createBitmap(WIDTH, bitmapHeight, Bitmap.Config.ARGB_8888)
|
||||||
for (layout in layouts) {
|
Canvas(bitmap).apply {
|
||||||
canvas.save()
|
drawColor(Color.WHITE)
|
||||||
canvas.translate(UNIT_F, y.toFloat())
|
heading.draw(this, X_PADDING, Y_PADDING)
|
||||||
layout.draw(canvas)
|
var y = Y_PADDING + heading.height + SPACING
|
||||||
canvas.restore()
|
for (layout in bodyLayouts) {
|
||||||
y += layout.height + UNIT
|
layout.draw(this, X_PADDING, y)
|
||||||
|
y += layout.height + SPACING
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val responseBody = Buffer().run {
|
val responseBody = Buffer().run {
|
||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream())
|
bitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream())
|
||||||
asResponseBody("image/jpeg".toMediaType())
|
asResponseBody("image/png".toMediaType())
|
||||||
}
|
}
|
||||||
return response.newBuilder().body(responseBody).build()
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user