From cc437b8cd450e142261485ab88589e91ec78da41 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 21 Mar 2025 13:13:58 +0200 Subject: [PATCH] [ExHentai] Pages preview support --- .../parsers/site/all/ExHentaiParser.kt | 35 ++++++++++++- .../kotatsu/parsers/util/CSSBackground.kt | 52 +++++++++++++++++++ .../koitharu/kotatsu/parsers/util/Jsoup.kt | 3 ++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/util/CSSBackground.kt diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt index af6f048f..06dc2a9b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt @@ -13,6 +13,7 @@ import org.jsoup.nodes.Element import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.bitmap.Rect import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser import org.koitharu.kotatsu.parsers.exception.AuthRequiredException @@ -258,7 +259,7 @@ internal class ExHentaiParser( MangaPage( id = generateUid(url), url = url, - preview = null, + preview = a.children().firstOrNull()?.extractPreview(), source = source, ) } @@ -318,6 +319,22 @@ internal class ExHentaiParser( ) } } + val imageRect = response.request.url.fragment?.split(',') + if (imageRect != null && imageRect.size == 4) { + // rect: top,left,right,bottom + return context.redrawImageResponse(response) { bitmap -> + val srcRect = Rect( + left = imageRect[0].toInt(), + top = imageRect[1].toInt(), + right = imageRect[2].toInt(), + bottom = imageRect[3].toInt(), + ) + val dstRect = Rect(0, 0, srcRect.width, srcRect.height) + val result = context.createBitmap(dstRect.width, dstRect.height) + result.drawBitmap(bitmap, srcRect, dstRect) + result + } + } return response } @@ -394,6 +411,22 @@ internal class ExHentaiParser( return result } + private fun Element.extractPreview(): String? { + val bg = backgroundOrNull() ?: return null + return buildString { + append(bg.url) + append('#') + // rect: left,top,right,bottom + append(bg.left) + append(',') + append(bg.top) + append(',') + append(bg.right) + append(',') + append(bg.bottom) + } + } + private fun getNextTimestamp(root: Element): Long { return root.getElementById("unext") ?.attrAsAbsoluteUrlOrNull("href") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/CSSBackground.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/CSSBackground.kt new file mode 100644 index 00000000..ecbf8cbf --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/CSSBackground.kt @@ -0,0 +1,52 @@ +package org.koitharu.kotatsu.core.parser + +import org.jsoup.nodes.Element +import org.koitharu.kotatsu.parsers.util.attrOrNull +import org.koitharu.kotatsu.parsers.util.nullIfEmpty +import org.koitharu.kotatsu.parsers.util.splitByWhitespace + +/** + * Utility class for parsing the `background` property of css + */ +public class CSSBackground private constructor( + public val url: String, + public val left: Int, + public val top: Int, + public val width: Int, + public val height: Int, +) { + + public val right: Int + get() = left + width + + public val bottom: Int + get() = top + height + + internal companion object { + + fun parse(element: Element): CSSBackground? { + val style = element.attrOrNull("style") ?: return null + val attrs = style.split(';').associate { + val trimmed = it.trim() + trimmed.substringBefore(':') to trimmed.substringAfter(':', "") + } + val width = attrs["width"]?.toPx() ?: return null + val height = attrs["height"]?.toPx() ?: return null + val bg = attrs["background"]?.substringAfter("url")?.splitByWhitespace() ?: return null + val url = bg.firstOrNull()?.removeSurrounding("(", ")")?.nullIfEmpty() ?: return null + val x = bg.getOrNull(1)?.toPx() ?: 0 + val y = bg.getOrNull(2)?.toPx() ?: 0 + return CSSBackground( + url = url, + left = -x, + top = y, + width = width, + height = height, + ) + } + + private fun String.toPx(): Int? { + return removeSuffix("px").toIntOrNull() + } + } +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt index 47f38e1b..5e9d39ac 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt @@ -7,6 +7,7 @@ import org.jsoup.nodes.Element import org.jsoup.select.Elements import org.jsoup.select.QueryParser import org.jsoup.select.Selector +import org.koitharu.kotatsu.core.parser.CSSBackground import org.koitharu.kotatsu.parsers.InternalParsersApi import org.koitharu.kotatsu.parsers.exception.ParseException import kotlin.contracts.contract @@ -189,6 +190,8 @@ public fun Element.requireSrc(): String = parseNotNull(src()) { "Image src not found" } +public fun Element.backgroundOrNull(): CSSBackground? = CSSBackground.parse(this) + public fun Element.metaValue(itemprop: String): String? = getElementsByAttributeValue("itemprop", itemprop) .firstNotNullOfOrNull { element -> element.attrOrNull("content")