Fix Webtoons & Tapas Author's Notes (#13304)
* Bump Webtoons extension version * Fix author's note API * Bump Tapas extension version * Fix author's note API * Tapas: Render author's note image locally Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com> * Tapas: Add new dependency to gradle * Tapas: revert dependency addition 94473a2 * Tapas: StaticLayout.draw without dependency * Webtoons: Render author's note image locally Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com> Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
This commit is contained in:
parent
8f0a4c8903
commit
356849909c
@ -2,7 +2,14 @@ package eu.kanade.tachiyomi.multisrc.webtoons
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.text.Layout
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.TextPaint
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
@ -25,20 +32,24 @@ import okhttp3.CookieJar
|
|||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Protocol
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.ceil
|
|
||||||
|
|
||||||
open class Webtoons(
|
open class Webtoons(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
@ -71,6 +82,7 @@ open class Webtoons(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.addInterceptor(TextInterceptor)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val day: String
|
private val day: String
|
||||||
@ -291,32 +303,6 @@ open class Webtoons(
|
|||||||
|
|
||||||
override fun chapterListRequest(manga: SManga) = GET("https://m.webtoons.com" + manga.url, mobileHeaders)
|
override fun chapterListRequest(manga: SManga) = GET("https://m.webtoons.com" + manga.url, mobileHeaders)
|
||||||
|
|
||||||
private fun wordwrap(t: String, lineWidth: Int) = buildString {
|
|
||||||
// TODO: Split off into library file or something, because Tapastic is using the exact same wordwrap and toImage functions
|
|
||||||
// src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt
|
|
||||||
val text = t.replace("\n", "\n ")
|
|
||||||
var charCount = 0
|
|
||||||
text.split(" ").forEach { w ->
|
|
||||||
if (w.contains("\n")) {
|
|
||||||
charCount = 0
|
|
||||||
}
|
|
||||||
if (charCount > lineWidth) {
|
|
||||||
append("\n")
|
|
||||||
charCount = 0
|
|
||||||
}
|
|
||||||
append("$w ")
|
|
||||||
charCount += w.length + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toImage(t: String, fontSize: Int, bold: Boolean = false): String {
|
|
||||||
val text = wordwrap(t.replace("&", "&").replace("\\s*<br>\\s*".toRegex(), "\n"), 65)
|
|
||||||
val imgHeight = (text.lines().size + 2) * fontSize * 1.3
|
|
||||||
return "https://placehold.jp/" + fontSize + "/ffffff/000000/1500x" + ceil(imgHeight).toInt() + ".png?" +
|
|
||||||
"css=%7B%22text-align%22%3A%22%20left%22%2C%22padding-left%22%3A%22%203%25%22" + (if (bold) "%2C%22font-weight%22%3A%22%20600%22" else "") + "%7D&" +
|
|
||||||
"text=" + Uri.encode(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
var pages = document.select("div#_imageList > img").mapIndexed { i, element -> Page(i, "", element.attr("data-url")) }
|
var pages = document.select("div#_imageList > img").mapIndexed { i, element -> Page(i, "", element.attr("data-url")) }
|
||||||
|
|
||||||
@ -324,13 +310,13 @@ open class Webtoons(
|
|||||||
val note = document.select("div.comment_area div.info_area p").text()
|
val note = document.select("div.comment_area div.info_area p").text()
|
||||||
|
|
||||||
if (note.isNotEmpty()) {
|
if (note.isNotEmpty()) {
|
||||||
val noteImage = toImage(note, 42)
|
|
||||||
|
|
||||||
val creator = document.select("div.creator_note span.author a").text().trim()
|
val creator = document.select("div.creator_note span.author a").text().trim()
|
||||||
val creatorImage = toImage("Author's Notes from $creator", 43, true)
|
|
||||||
|
|
||||||
pages = pages + Page(pages.size, "", creatorImage)
|
pages = pages + Page(
|
||||||
pages = pages + Page(pages.size, "", noteImage)
|
pages.size, "",
|
||||||
|
"http://note/" + Uri.encode(creator) + "/" + Uri.encode(note)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,4 +345,100 @@ open class Webtoons(
|
|||||||
const val URL_SEARCH_PREFIX = "url:"
|
const val URL_SEARCH_PREFIX = "url:"
|
||||||
private const val SHOW_AUTHORS_NOTES_KEY = "showAuthorsNotes"
|
private const val SHOW_AUTHORS_NOTES_KEY = "showAuthorsNotes"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Split off into library file or something, because Webtoons is using the exact same TextInterceptor
|
||||||
|
// src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt
|
||||||
|
object TextInterceptor : Interceptor {
|
||||||
|
// With help from:
|
||||||
|
// https://github.com/tachiyomiorg/tachiyomi-extensions/pull/13304#issuecomment-1234532897
|
||||||
|
// https://medium.com/over-engineering/drawing-multiline-text-to-canvas-on-android-9b98f0bfa16a
|
||||||
|
|
||||||
|
// Designer values:
|
||||||
|
private const val WIDTH: Int = 1000
|
||||||
|
private const val X_PADDING: Float = 50f
|
||||||
|
private const val Y_PADDING: Float = 25f
|
||||||
|
private const val HEADING_FONT_SIZE: Float = 36f
|
||||||
|
private const val BODY_FONT_SIZE: Float = 30f
|
||||||
|
private const val SPACING_MULT: Float = 1.1f
|
||||||
|
private const val SPACING_ADD: Float = 2f
|
||||||
|
|
||||||
|
// No need to touch this one:
|
||||||
|
private const val HOST = "note"
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
val url = request.url
|
||||||
|
if (url.host != HOST) return chain.proceed(request)
|
||||||
|
|
||||||
|
val creator = textFixer("Author's Notes from ${url.pathSegments[0]}")
|
||||||
|
val story = textFixer(url.pathSegments[1])
|
||||||
|
|
||||||
|
// Heading
|
||||||
|
val paintHeading = TextPaint().apply {
|
||||||
|
color = Color.BLACK
|
||||||
|
textSize = HEADING_FONT_SIZE
|
||||||
|
typeface = Typeface.DEFAULT_BOLD
|
||||||
|
isAntiAlias = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val heading: StaticLayout = StaticLayout(
|
||||||
|
creator, paintHeading, (WIDTH - 2 * X_PADDING).toInt(),
|
||||||
|
Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true
|
||||||
|
)
|
||||||
|
|
||||||
|
// Body
|
||||||
|
val paintBody = TextPaint().apply {
|
||||||
|
color = Color.BLACK
|
||||||
|
textSize = BODY_FONT_SIZE
|
||||||
|
typeface = Typeface.DEFAULT
|
||||||
|
isAntiAlias = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val body: StaticLayout = StaticLayout(
|
||||||
|
story, paintBody, (WIDTH - 2 * X_PADDING).toInt(),
|
||||||
|
Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true
|
||||||
|
)
|
||||||
|
|
||||||
|
// Image building
|
||||||
|
val imgHeight: Int = (heading.height + body.height + 2 * Y_PADDING).toInt()
|
||||||
|
val bitmap: Bitmap = Bitmap.createBitmap(WIDTH, imgHeight, Bitmap.Config.ARGB_8888)
|
||||||
|
val canvas: Canvas = Canvas(bitmap)
|
||||||
|
|
||||||
|
// Image drawing
|
||||||
|
canvas.drawColor(Color.WHITE)
|
||||||
|
heading.draw(canvas, X_PADDING, Y_PADDING)
|
||||||
|
body.draw(canvas, X_PADDING, Y_PADDING + heading.height.toFloat())
|
||||||
|
|
||||||
|
// Image converting & returning
|
||||||
|
val stream: ByteArrayOutputStream = ByteArrayOutputStream()
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream)
|
||||||
|
val responseBody = stream.toByteArray().toResponseBody("image/png".toMediaType())
|
||||||
|
return Response.Builder()
|
||||||
|
.request(request)
|
||||||
|
.protocol(Protocol.HTTP_1_1)
|
||||||
|
.code(200)
|
||||||
|
.message("OK")
|
||||||
|
.body(responseBody)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun textFixer(t: String): String {
|
||||||
|
return t
|
||||||
|
.replace("&", "&")
|
||||||
|
.replace("'", "'")
|
||||||
|
.replace(""", "\"")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("\\s*<br>\\s*".toRegex(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StaticLayout.draw(canvas: Canvas, x: Float, y: Float) {
|
||||||
|
canvas.save()
|
||||||
|
canvas.translate(x, y)
|
||||||
|
this.draw(canvas)
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class WebtoonsGenerator : ThemeSourceGenerator {
|
|||||||
override val baseVersionCode: Int = 2
|
override val baseVersionCode: Int = 2
|
||||||
|
|
||||||
override val sources = listOf(
|
override val sources = listOf(
|
||||||
MultiLang("Webtoons.com", "https://www.webtoons.com", listOf("en", "fr", "es", "id", "th", "zh-Hant", "de"), className = "WebtoonsFactory", pkgName = "webtoons", overrideVersionCode = 33),
|
MultiLang("Webtoons.com", "https://www.webtoons.com", listOf("en", "fr", "es", "id", "th", "zh-Hant", "de"), className = "WebtoonsFactory", pkgName = "webtoons", overrideVersionCode = 34),
|
||||||
SingleLang("Dongman Manhua", "https://www.dongmanmanhua.cn", "zh")
|
SingleLang("Dongman Manhua", "https://www.dongmanmanhua.cn", "zh")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ ext {
|
|||||||
extName = 'Tapas'
|
extName = 'Tapas'
|
||||||
pkgNameSuffix = 'en.tapastic'
|
pkgNameSuffix = 'en.tapastic'
|
||||||
extClass = '.Tapastic'
|
extClass = '.Tapastic'
|
||||||
extVersionCode = 16
|
extVersionCode = 17
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -2,7 +2,15 @@ package eu.kanade.tachiyomi.extension.en.tapastic
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Bitmap.CompressFormat
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.text.Layout
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.TextPaint
|
||||||
import android.webkit.CookieManager
|
import android.webkit.CookieManager
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
@ -25,18 +33,22 @@ import okhttp3.CookieJar
|
|||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Protocol
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.ceil
|
|
||||||
|
|
||||||
class Tapastic : ConfigurableSource, ParsedHttpSource() {
|
class Tapastic : ConfigurableSource, ParsedHttpSource() {
|
||||||
|
|
||||||
@ -96,6 +108,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.addInterceptor(TextInterceptor)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
private val preferences: SharedPreferences by lazy {
|
||||||
@ -339,32 +352,6 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
|
|||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
|
|
||||||
// TODO: Split off into library file or something, because Webtoons is using the exact same wordwrap and toImage functions
|
|
||||||
// multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt
|
|
||||||
private fun wordwrap(t: String, lineWidth: Int) = buildString {
|
|
||||||
val text = t.replace("\n", "\n ")
|
|
||||||
var charCount = 0
|
|
||||||
text.split(" ").forEach { w ->
|
|
||||||
if (w.contains("\n")) {
|
|
||||||
charCount = 0
|
|
||||||
}
|
|
||||||
if (charCount > lineWidth) {
|
|
||||||
append("\n")
|
|
||||||
charCount = 0
|
|
||||||
}
|
|
||||||
append("$w ")
|
|
||||||
charCount += w.length + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toImage(t: String, fontSize: Int, bold: Boolean = false): String {
|
|
||||||
val text = wordwrap(t.replace("&", "&").replace("\\s*<br>\\s*".toRegex(), "\n"), 65)
|
|
||||||
val imgHeight = (text.lines().size + 2) * fontSize * 1.3
|
|
||||||
return "https://placehold.jp/" + fontSize + "/ffffff/000000/1500x" + ceil(imgHeight).toInt() + ".png?" +
|
|
||||||
"css=%7B%22text-align%22%3A%22%20left%22%2C%22padding-left%22%3A%22%203%25%22" + (if (bold) "%2C%22font-weight%22%3A%22%20600%22" else "") + "%7D&" +
|
|
||||||
"text=" + Uri.encode(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
var pages = document.select("img.content__img").mapIndexed { i, img ->
|
var pages = document.select("img.content__img").mapIndexed { i, img ->
|
||||||
Page(i, "", img.let { if (it.hasAttr("data-src")) it.attr("abs:data-src") else it.attr("abs:src") })
|
Page(i, "", img.let { if (it.hasAttr("data-src")) it.attr("abs:data-src") else it.attr("abs:src") })
|
||||||
@ -374,13 +361,12 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
|
|||||||
val episodeStory = document.select("p.js-episode-story").html()
|
val episodeStory = document.select("p.js-episode-story").html()
|
||||||
|
|
||||||
if (episodeStory.isNotEmpty()) {
|
if (episodeStory.isNotEmpty()) {
|
||||||
val storyImage = toImage(episodeStory, 42)
|
|
||||||
|
|
||||||
val creator = document.select("a.name.js-fb-tracking")[0].text()
|
val creator = document.select("a.name.js-fb-tracking")[0].text()
|
||||||
val creatorImage = toImage("Author's Notes from $creator", 43, true)
|
|
||||||
|
|
||||||
pages = pages + Page(pages.size, "", creatorImage)
|
pages = pages + Page(
|
||||||
pages = pages + Page(pages.size, "", storyImage)
|
pages.size, "",
|
||||||
|
"http://note/" + Uri.encode(creator) + "/" + Uri.encode(episodeStory)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pages
|
return pages
|
||||||
@ -534,4 +520,100 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
|
|||||||
private const val SHOW_LOCK_PREF_KEY = "showChapterLock"
|
private const val SHOW_LOCK_PREF_KEY = "showChapterLock"
|
||||||
private const val SHOW_AUTHORS_NOTES_KEY = "showAuthorsNotes"
|
private const val SHOW_AUTHORS_NOTES_KEY = "showAuthorsNotes"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Split off into library file or something, because Webtoons is using the exact same TextInterceptor
|
||||||
|
// multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt
|
||||||
|
object TextInterceptor : Interceptor {
|
||||||
|
// With help from:
|
||||||
|
// https://github.com/tachiyomiorg/tachiyomi-extensions/pull/13304#issuecomment-1234532897
|
||||||
|
// https://medium.com/over-engineering/drawing-multiline-text-to-canvas-on-android-9b98f0bfa16a
|
||||||
|
|
||||||
|
// Designer values:
|
||||||
|
private const val WIDTH: Int = 1000
|
||||||
|
private const val X_PADDING: Float = 50f
|
||||||
|
private const val Y_PADDING: Float = 25f
|
||||||
|
private const val HEADING_FONT_SIZE: Float = 36f
|
||||||
|
private const val BODY_FONT_SIZE: Float = 30f
|
||||||
|
private const val SPACING_MULT: Float = 1.1f
|
||||||
|
private const val SPACING_ADD: Float = 2f
|
||||||
|
|
||||||
|
// No need to touch this one:
|
||||||
|
private const val HOST = "note"
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
val url = request.url
|
||||||
|
if (url.host != HOST) return chain.proceed(request)
|
||||||
|
|
||||||
|
val creator = textFixer("Author's Notes from ${url.pathSegments[0]}")
|
||||||
|
val story = textFixer(url.pathSegments[1])
|
||||||
|
|
||||||
|
// Heading
|
||||||
|
val paintHeading = TextPaint().apply {
|
||||||
|
color = Color.BLACK
|
||||||
|
textSize = HEADING_FONT_SIZE
|
||||||
|
typeface = Typeface.DEFAULT_BOLD
|
||||||
|
isAntiAlias = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val heading: StaticLayout = StaticLayout(
|
||||||
|
creator, paintHeading, (WIDTH - 2 * X_PADDING).toInt(),
|
||||||
|
Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true
|
||||||
|
)
|
||||||
|
|
||||||
|
// Body
|
||||||
|
val paintBody = TextPaint().apply {
|
||||||
|
color = Color.BLACK
|
||||||
|
textSize = BODY_FONT_SIZE
|
||||||
|
typeface = Typeface.DEFAULT
|
||||||
|
isAntiAlias = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val body: StaticLayout = StaticLayout(
|
||||||
|
story, paintBody, (WIDTH - 2 * X_PADDING).toInt(),
|
||||||
|
Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true
|
||||||
|
)
|
||||||
|
|
||||||
|
// Image building
|
||||||
|
val imgHeight: Int = (heading.height + body.height + 2 * Y_PADDING).toInt()
|
||||||
|
val bitmap: Bitmap = Bitmap.createBitmap(WIDTH, imgHeight, Bitmap.Config.ARGB_8888)
|
||||||
|
val canvas: Canvas = Canvas(bitmap)
|
||||||
|
|
||||||
|
// Image drawing
|
||||||
|
canvas.drawColor(Color.WHITE)
|
||||||
|
heading.draw(canvas, X_PADDING, Y_PADDING)
|
||||||
|
body.draw(canvas, X_PADDING, Y_PADDING + heading.height.toFloat())
|
||||||
|
|
||||||
|
// Image converting & returning
|
||||||
|
val stream: ByteArrayOutputStream = ByteArrayOutputStream()
|
||||||
|
bitmap.compress(CompressFormat.PNG, 0, stream)
|
||||||
|
val responseBody = stream.toByteArray().toResponseBody("image/png".toMediaType())
|
||||||
|
return Response.Builder()
|
||||||
|
.request(request)
|
||||||
|
.protocol(Protocol.HTTP_1_1)
|
||||||
|
.code(200)
|
||||||
|
.message("OK")
|
||||||
|
.body(responseBody)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun textFixer(t: String): String {
|
||||||
|
return t
|
||||||
|
.replace("&", "&")
|
||||||
|
.replace("'", "'")
|
||||||
|
.replace(""", "\"")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("\\s*<br>\\s*".toRegex(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
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