File diff suppressed because it is too large
Load Diff
@@ -9,11 +9,22 @@ object AppPattern {
|
||||
val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}")
|
||||
|
||||
//匹配格式化后的图片格式
|
||||
val imgPattern: Pattern =
|
||||
Pattern.compile("<img[^>]*src=['\"]([^'\"]*(?:['\"][^>]+\\})?)['\"][^>]*>")
|
||||
val imgPattern: Pattern = Pattern.compile("<img[^>]*src=\"([^\"]*(?:\"[^>]+\\})?)\"[^>]*>")
|
||||
|
||||
//dataURL图片类型
|
||||
val dataUriRegex = Regex("^data:.*?;base64,(.*)")
|
||||
//提取标题中的段评
|
||||
val imgRegex = Regex("(.*)((?:data|https?):[\\s\\S]+)$")
|
||||
//自定义图片样式
|
||||
val imgStyRegex = Regex("style[\"'\\s]*:\\s*[\"']([^\"']*)[\"']")
|
||||
//匹配章节信息中的字数
|
||||
val wordCountRegex = Regex("(?:^|字数[::、]?|\\s+)([0-9万千百\\.]{1,6}字)")
|
||||
|
||||
//正文不计入字数的字符
|
||||
val noWordCountRegex = Regex("[\\s\\u200B-\\u200F\\uFEFF]")
|
||||
|
||||
//提取链接中的域名
|
||||
val domainRegex = Regex("^https?://([^:/]+)",RegexOption.IGNORE_CASE)
|
||||
|
||||
val nameRegex = Regex("\\s+作\\s*者.*|\\s+\\S+\\s+著")
|
||||
val authorRegex = Regex("^\\s*作\\s*者[::\\s]+|\\s+著")
|
||||
@@ -55,4 +66,4 @@ object AppPattern {
|
||||
val regexCharRegex = "[{}()\\[\\].+*?^$\\\\|]".toRegex()
|
||||
|
||||
val LFRegex = "\n".toRegex()
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ val appDb by lazy {
|
||||
}
|
||||
|
||||
@Database(
|
||||
version = 75,
|
||||
version = 76,
|
||||
exportSchema = true,
|
||||
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
|
||||
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
|
||||
@@ -106,6 +106,7 @@ val appDb by lazy {
|
||||
AutoMigration(from = 72, to = 73),
|
||||
AutoMigration(from = 73, to = 74),
|
||||
AutoMigration(from = 74, to = 75),
|
||||
AutoMigration(from = 75, to = 76)
|
||||
]
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
@@ -55,7 +55,8 @@ data class BookChapter(
|
||||
var end: Long? = null, // 章节终止位置
|
||||
var startFragmentId: String? = null, //EPUB书籍当前章节的fragmentId
|
||||
var endFragmentId: String? = null, //EPUB书籍下一章节的fragmentId
|
||||
var variable: String? = null //变量
|
||||
var variable: String? = null, //变量
|
||||
var reviewImg: String? = null //段评图标
|
||||
) : Parcelable, RuleDataInterface {
|
||||
|
||||
@delegate:Transient
|
||||
|
||||
@@ -63,19 +63,6 @@ object BookContent {
|
||||
analyzeRule.setChapter(bookChapter)
|
||||
analyzeRule.setNextChapterUrl(mNextChapterUrl)
|
||||
coroutineContext.ensureActive()
|
||||
val titleRule = contentRule.title
|
||||
if (!titleRule.isNullOrBlank()) {
|
||||
val title = analyzeRule.runCatching {
|
||||
getString(titleRule)
|
||||
}.onFailure {
|
||||
Debug.log(bookSource.bookSourceUrl, "获取标题出错, ${it.localizedMessage}")
|
||||
}.getOrNull()
|
||||
if (!title.isNullOrBlank()) {
|
||||
bookChapter.title = title
|
||||
bookChapter.titleMD5 = null
|
||||
appDb.bookChapterDao.update(bookChapter)
|
||||
}
|
||||
}
|
||||
var contentData = analyzeContent(
|
||||
book, baseUrl, redirectUrl, body, contentRule, bookChapter, bookSource, mNextChapterUrl
|
||||
)
|
||||
@@ -135,6 +122,30 @@ object BookContent {
|
||||
}
|
||||
}
|
||||
var contentStr = contentList.joinToString("\n")
|
||||
val titleRule = contentRule.title //先正文再章节名称
|
||||
if (!titleRule.isNullOrBlank()) {
|
||||
var title = analyzeRule.runCatching {
|
||||
getString(titleRule)
|
||||
}.onFailure {
|
||||
Debug.log(bookSource.bookSourceUrl, "获取标题出错, ${it.localizedMessage}")
|
||||
}.getOrNull()
|
||||
if (!title.isNullOrBlank()) {
|
||||
val matchResult = AppPattern.imgRegex.find(title)
|
||||
if (matchResult != null) {
|
||||
matchResult.groupValues[1]
|
||||
val (group1,group2) = matchResult.destructured
|
||||
title = if (group1 != "") {
|
||||
group1
|
||||
} else {
|
||||
bookChapter.title
|
||||
}
|
||||
bookChapter.reviewImg = group2
|
||||
}
|
||||
bookChapter.title = title
|
||||
bookChapter.titleMD5 = null
|
||||
appDb.bookChapterDao.update(bookChapter)
|
||||
}
|
||||
}
|
||||
//全文替换
|
||||
val replaceRegex = contentRule.replaceRegex
|
||||
if (!replaceRegex.isNullOrEmpty()) {
|
||||
|
||||
@@ -46,10 +46,10 @@ import java.util.Locale
|
||||
@Suppress("DEPRECATION", "ConstPropertyName")
|
||||
object ChapterProvider {
|
||||
//用于图片字的替换
|
||||
const val srcReplaceChar = "▩"
|
||||
const val srcReplaceChar = "⼁"//▩ 丨
|
||||
|
||||
//用于评论按钮的替换
|
||||
const val reviewChar = "▨"
|
||||
const val reviewChar = "꧁"
|
||||
|
||||
const val indentChar = " "
|
||||
|
||||
|
||||
+126
-45
@@ -4,8 +4,11 @@ import android.graphics.Paint
|
||||
import android.text.Layout
|
||||
import android.text.StaticLayout
|
||||
import android.text.TextPaint
|
||||
import android.util.Size
|
||||
import io.legato.kazusa.constant.AppLog
|
||||
import io.legato.kazusa.constant.AppPattern
|
||||
import io.legato.kazusa.constant.AppPattern.noWordCountRegex
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.data.entities.Book
|
||||
import io.legato.kazusa.data.entities.BookChapter
|
||||
import io.legato.kazusa.help.book.BookContent
|
||||
@@ -21,6 +24,7 @@ import io.legato.kazusa.ui.book.read.page.entities.TextLine
|
||||
import io.legato.kazusa.ui.book.read.page.entities.TextPage
|
||||
import io.legato.kazusa.ui.book.read.page.entities.column.ImageColumn
|
||||
import io.legato.kazusa.ui.book.read.page.entities.column.TextColumn
|
||||
import io.legato.kazusa.utils.StringUtils
|
||||
import io.legato.kazusa.utils.dpToPx
|
||||
import io.legato.kazusa.utils.fastSum
|
||||
import io.legato.kazusa.utils.getTextWidthsCompat
|
||||
@@ -202,13 +206,25 @@ class TextChapterLayout(
|
||||
if (titleMode != 2 || bookChapter.isVolume || contents.isEmpty()) {
|
||||
//标题非隐藏
|
||||
displayTitle.splitNotBlank("\n").forEach { text ->
|
||||
val srcList = LinkedList<String>()
|
||||
val reviewImg = bookChapter.reviewImg
|
||||
var reviewTxt = ""
|
||||
if (reviewImg != null) {
|
||||
srcList.add(reviewImg)
|
||||
reviewTxt = if (reviewImg.contains("TEXT")) {
|
||||
ChapterProvider.reviewChar
|
||||
} else {
|
||||
ChapterProvider.srcReplaceChar
|
||||
}
|
||||
}
|
||||
setTypeText(
|
||||
book,
|
||||
if (AppConfig.enableReview) text + ChapterProvider.reviewChar else text,
|
||||
text + reviewTxt,
|
||||
titlePaint,
|
||||
titlePaintTextHeight,
|
||||
titlePaintFontMetrics,
|
||||
imageStyle,
|
||||
srcList = srcList.ifEmpty { null },
|
||||
isTitle = true,
|
||||
emptyContent = contents.isEmpty(),
|
||||
isVolumeTitle = bookChapter.isVolume
|
||||
@@ -226,11 +242,12 @@ class TextChapterLayout(
|
||||
|
||||
val sb = StringBuffer()
|
||||
var isSetTypedImage = false
|
||||
var wordCount = 0
|
||||
contents.forEach { content ->
|
||||
coroutineContext.ensureActive()
|
||||
if (isTextImageStyle) {
|
||||
//图片样式为文字嵌入类型
|
||||
var text = content.replace(ChapterProvider.srcReplaceChar, "▣")
|
||||
var text = content.replace(ChapterProvider.srcReplaceChar, "丨") //▣
|
||||
val srcList = LinkedList<String>()
|
||||
sb.setLength(0)
|
||||
val matcher = AppPattern.imgPattern.matcher(text)
|
||||
@@ -242,6 +259,7 @@ class TextChapterLayout(
|
||||
}
|
||||
matcher.appendTail(sb)
|
||||
text = sb.toString()
|
||||
wordCount += text.replace(noWordCountRegex,"").length
|
||||
setTypeText(
|
||||
book,
|
||||
text,
|
||||
@@ -257,29 +275,80 @@ class TextChapterLayout(
|
||||
prepareNextPageIfNeed()
|
||||
}
|
||||
var start = 0
|
||||
val srcList = LinkedList<String>()
|
||||
sb.setLength(0)
|
||||
var isFirstLine = true
|
||||
if (content.contains("<img")) {
|
||||
val matcher = AppPattern.imgPattern.matcher(content)
|
||||
val text = content.replace(ChapterProvider.srcReplaceChar, "丨")
|
||||
val matcher = AppPattern.imgPattern.matcher(text)
|
||||
while (matcher.find()) {
|
||||
coroutineContext.ensureActive()
|
||||
val text = content.substring(start, matcher.start())
|
||||
if (text.isNotBlank()) {
|
||||
setTypeText(
|
||||
val imgSrc = matcher.group(1)!!
|
||||
var iStyle = imageStyle
|
||||
var isSmallImage = true
|
||||
val matchResult = AppPattern.imgStyRegex.find(imgSrc)
|
||||
var imgSize: Size? = null
|
||||
if (matchResult != null) {
|
||||
val styleValue = matchResult.groupValues[1].trim()
|
||||
if (styleValue == "TEXT" && matcher.end() == text.length) {
|
||||
iStyle = "TEXT"
|
||||
wordCount--
|
||||
}
|
||||
else if (styleValue.equals("text", true)) {
|
||||
iStyle = "text"
|
||||
}
|
||||
else {
|
||||
imgSize = ImageProvider.getImageSize(book, imgSrc, ReadBook.bookSource)
|
||||
iStyle = styleValue
|
||||
isSmallImage = false
|
||||
}
|
||||
}
|
||||
else {
|
||||
imgSize = ImageProvider.getImageSize(book, imgSrc, ReadBook.bookSource)
|
||||
if (imgSize.width < 80 && imgSize.height < 80) {
|
||||
iStyle = "text"
|
||||
}
|
||||
else {
|
||||
isSmallImage = false
|
||||
}
|
||||
}
|
||||
if (start < matcher.start()) {
|
||||
sb.append(text.substring(start, matcher.start()))
|
||||
}
|
||||
if (isSmallImage) {
|
||||
sb.append(
|
||||
if (iStyle == "TEXT")
|
||||
ChapterProvider.reviewChar
|
||||
else
|
||||
ChapterProvider.srcReplaceChar
|
||||
)
|
||||
srcList.add(imgSrc)
|
||||
} else {
|
||||
val textBefore = sb.toString()
|
||||
if (textBefore.isNotBlank()) {
|
||||
wordCount += textBefore.replace(noWordCountRegex,"").length
|
||||
setTypeText(
|
||||
book,
|
||||
sb.toString(),
|
||||
contentPaint,
|
||||
contentPaintTextHeight,
|
||||
contentPaintFontMetrics,
|
||||
"TEXT",
|
||||
isFirstLine = isFirstLine,
|
||||
srcList = srcList
|
||||
)
|
||||
sb.setLength(0)
|
||||
isFirstLine=false
|
||||
}
|
||||
setTypeImage(
|
||||
book,
|
||||
text,
|
||||
contentPaint,
|
||||
imgSrc,
|
||||
contentPaintTextHeight,
|
||||
contentPaintFontMetrics,
|
||||
imageStyle,
|
||||
isFirstLine = start == 0
|
||||
iStyle,
|
||||
imgSize!!
|
||||
)
|
||||
isSetTypedImage = true
|
||||
}
|
||||
setTypeImage(
|
||||
book,
|
||||
matcher.group(1)!!,
|
||||
contentPaintTextHeight,
|
||||
imageStyle
|
||||
)
|
||||
isSetTypedImage = true
|
||||
start = matcher.end()
|
||||
}
|
||||
}
|
||||
@@ -288,23 +357,30 @@ class TextChapterLayout(
|
||||
isSetTypedImage = false
|
||||
prepareNextPageIfNeed()
|
||||
}
|
||||
val text = content.substring(start, content.length)
|
||||
if (text.isNotBlank()) {
|
||||
setTypeText(
|
||||
book,
|
||||
if (AppConfig.enableReview) text + ChapterProvider.reviewChar else text,
|
||||
contentPaint,
|
||||
contentPaintTextHeight,
|
||||
contentPaintFontMetrics,
|
||||
imageStyle,
|
||||
isFirstLine = start == 0
|
||||
)
|
||||
}
|
||||
val textAfter = content.substring(start, content.length)
|
||||
sb.append(textAfter)
|
||||
}
|
||||
val text = sb.toString()
|
||||
if (text.isNotBlank()) {
|
||||
wordCount += text.replace(noWordCountRegex,"").length
|
||||
setTypeText(
|
||||
book,
|
||||
if (AppConfig.enableReview) text + ChapterProvider.reviewChar else text,
|
||||
contentPaint,
|
||||
contentPaintTextHeight,
|
||||
contentPaintFontMetrics,
|
||||
"TEXT",
|
||||
isFirstLine = start == 0,
|
||||
srcList = srcList.ifEmpty { null }
|
||||
)
|
||||
}
|
||||
}
|
||||
pendingTextPage.lines.last().isParagraphEnd = true
|
||||
stringBuilder.append("\n")
|
||||
}
|
||||
val chapterWordCount = StringUtils.wordCountFormat(wordCount.toString())
|
||||
bookChapter.wordCount = chapterWordCount
|
||||
appDb.bookChapterDao.upWordCount(bookChapter.bookUrl, bookChapter.url, chapterWordCount)
|
||||
val textPage = pendingTextPage
|
||||
val endPadding = 20.dpToPx()
|
||||
val durYPadding = durY + endPadding
|
||||
@@ -327,8 +403,8 @@ class TextChapterLayout(
|
||||
src: String,
|
||||
textHeight: Float,
|
||||
imageStyle: String?,
|
||||
size: Size
|
||||
) {
|
||||
val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource)
|
||||
if (size.width > 0 && size.height > 0) {
|
||||
prepareNextPageIfNeed(durY)
|
||||
var height = size.height
|
||||
@@ -375,13 +451,19 @@ class TextChapterLayout(
|
||||
durY += height
|
||||
textLine.lineBottom = durY + paddingTop
|
||||
val (start, end) = if (visibleWidth > width) {
|
||||
val adjustWidth = (visibleWidth - width) / 2f
|
||||
Pair(adjustWidth, adjustWidth + width)
|
||||
when (imageStyle?.uppercase()) {
|
||||
"RIGHT" -> Pair(visibleWidth - width, visibleWidth)
|
||||
"LEFT" -> Pair(0f, width)
|
||||
else -> {
|
||||
val adjustWidth = (visibleWidth - width) / 2f
|
||||
Pair(adjustWidth, adjustWidth + width)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Pair(0f, width.toFloat())
|
||||
Pair(0f, width)
|
||||
}
|
||||
textLine.addColumn(
|
||||
ImageColumn(start = absStartX + start, end = absStartX + end, src = src)
|
||||
ImageColumn(start = absStartX + start.toFloat(), end = absStartX + end.toFloat(), src = src)
|
||||
)
|
||||
calcTextLinePosition(textPages, textLine, stringBuilder.length)
|
||||
stringBuilder.append(" ") // 确保翻页时索引计算正确
|
||||
@@ -697,7 +779,7 @@ class TextChapterLayout(
|
||||
srcList: LinkedList<String>?
|
||||
) {
|
||||
val column = when {
|
||||
srcList != null && char == ChapterProvider.srcReplaceChar -> {
|
||||
!srcList.isNullOrEmpty() && (char == ChapterProvider.srcReplaceChar || char == ChapterProvider.reviewChar && isLineEnd) -> {
|
||||
val src = srcList.removeFirst()
|
||||
ImageProvider.cacheImage(book, src, ReadBook.bookSource)
|
||||
ImageColumn(
|
||||
@@ -706,14 +788,13 @@ class TextChapterLayout(
|
||||
src = src
|
||||
)
|
||||
}
|
||||
|
||||
// isLineEnd && char == ChapterProvider.reviewChar -> {
|
||||
// ReviewColumn(
|
||||
// start = absStartX + xStart,
|
||||
// end = absStartX + xEnd,
|
||||
// count = 100
|
||||
// )
|
||||
// }
|
||||
/*isLineEnd && char == ChapterProvider.reviewChar -> {
|
||||
ReviewColumn(
|
||||
start = absStartX + xStart,
|
||||
end = absStartX + xEnd,
|
||||
count = 10
|
||||
)
|
||||
}*/
|
||||
|
||||
else -> {
|
||||
TextColumn(
|
||||
@@ -820,4 +901,4 @@ class TextChapterLayout(
|
||||
return code == 8203 || code == 8204 || code == 8205 || code == 8288
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user