Merge pull request #48 from gedoor/master

merge
pull/889/head
hoodie13 4 years ago committed by GitHub
commit 897955e4c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      app/src/main/assets/defaultData/rssSources.json
  2. 6
      app/src/main/assets/updateLog.md
  3. 2
      app/src/main/java/io/legado/app/data/dao/EpubChapterDao.kt
  4. 16
      app/src/main/java/io/legado/app/data/entities/Book.kt
  5. 2
      app/src/main/java/io/legado/app/help/AppConfig.kt
  6. 2
      app/src/main/java/io/legado/app/help/LocalConfig.kt
  7. 2
      app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt
  8. 32
      app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
  9. 33
      app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt
  10. 4
      app/src/main/java/io/legado/app/receiver/SharedReceiverActivity.kt
  11. 11
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  12. 11
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  13. 44
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  14. 8
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt
  15. 6
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt
  16. 144
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
  17. 158
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt
  18. 11
      app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt
  19. 2
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  20. 5
      app/src/main/res/menu/book_read.xml
  21. 1
      app/src/main/res/values-zh-rHK/strings.xml
  22. 1
      app/src/main/res/values-zh-rTW/strings.xml
  23. 1
      app/src/main/res/values-zh/strings.xml
  24. 1
      app/src/main/res/values/strings.xml
  25. 2
      app/src/main/res/xml/shortcuts.xml
  26. 1
      avd11.bat

@ -17,5 +17,14 @@
"sourceName": "阅读下载", "sourceName": "阅读下载",
"sourceUrl": "https:\/\/github.com\/gedoor\/legado\/releases", "sourceUrl": "https:\/\/github.com\/gedoor\/legado\/releases",
"style": "" "style": ""
},
{
"customOrder": 1,
"enableJs": true,
"enabled": true,
"singleUrl": true,
"sourceIcon": "http:\/\/mmbiz.qpic.cn\/mmbiz_png\/hpfMV8hEuL2eS6vnCxvTzoOiaCAibV6exBzJWq9xMic9xDg3YXAick87tsfafic0icRwkQ5ibV0bJ84JtSuxhPuEDVquA\/0?wx_fmt=png",
"sourceName": "小说拾遗",
"sourceUrl": "snssdk1128:\/\/user\/profile\/562564899806367"
} }
] ]

@ -3,11 +3,15 @@
* 关注合作公众号 **[小说拾遗]** 获取好看的小说。 * 关注合作公众号 **[小说拾遗]** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2021/03/01** **2021/03/08**
* 阅读页面停留10分钟之后自动备份进度 * 阅读页面停留10分钟之后自动备份进度
* 添加了针对中文的断行排版处理-by hoodie13, 需要再阅读界面设置里手动开启 * 添加了针对中文的断行排版处理-by hoodie13, 需要再阅读界面设置里手动开启
* 添加朗读快捷方式 * 添加朗读快捷方式
* 优化Epub解析 by hoodie13 * 优化Epub解析 by hoodie13
* epub书籍增加cache by hoodie13
* 修复切换书籍或者章节时的断言崩溃问题。看漫画容易复现。 by hoodie13
* 修正增加书签alert的正文内容较多时,确定键溢出屏幕问题 by hoodie13
* 图片样式添加TEXT, 阅读界面菜单里可以选择图片样式
**2021/02/26** **2021/02/26**
* 添加反转内容功能 * 添加反转内容功能

@ -12,7 +12,7 @@ interface EpubChapterDao {
fun getCnt(bookUrl: String): Int fun getCnt(bookUrl: String): Int
@Query("select * from epubChapters Where bookUrl = :bookUrl and parentHref = :parentHref ") @Query("select * from epubChapters Where bookUrl = :bookUrl and parentHref = :parentHref ")
fun get(bookUrl: String, parentHref: String): List<EpubChapter>? fun get(bookUrl: String, parentHref: String): List<EpubChapter>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg chapter: EpubChapter) fun insert(vararg chapter: EpubChapter)

@ -146,6 +146,14 @@ data class Book(
config().pageAnim = pageAnim config().pageAnim = pageAnim
} }
fun getImageStyle(): String? {
return config().imageStyle
}
fun setImageStyle(imageStyle: String?) {
config().imageStyle = imageStyle
}
fun getDelParagraph(): Int { fun getDelParagraph(): Int {
return config().delParagraph return config().delParagraph
} }
@ -228,15 +236,19 @@ data class Book(
const val hTag = 2L const val hTag = 2L
const val rubyTag = 4L const val rubyTag = 4L
const val imgTag = 8L const val imgTag = 8L
const val imgStyleDefault = "DEFAULT"
const val imgStyleFull = "FULL"
const val imgStyleText = "TEXT"
} }
@Parcelize @Parcelize
data class ReadConfig( data class ReadConfig(
var pageAnim: Int = -1, var pageAnim: Int = -1,
var reSegment: Boolean = false, var reSegment: Boolean = false,
var useReplaceRule: Boolean = AppConfig.replaceEnableDefault, // 正文使用净化替换规则 var imageStyle: String? = null,
var useReplaceRule: Boolean = AppConfig.replaceEnableDefault,// 正文使用净化替换规则
var delParagraph: Int = 0,//去除段首 var delParagraph: Int = 0,//去除段首
var delTag: Long = 0L//去除标签 var delTag: Long = 0L,//去除标签
) : Parcelable ) : Parcelable
class Converters { class Converters {

@ -175,6 +175,8 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
val preDownload get() = appCtx.getPrefBoolean(PreferKey.preDownload, true) val preDownload get() = appCtx.getPrefBoolean(PreferKey.preDownload, true)
val mediaButtonOnExit get() = appCtx.getPrefBoolean("mediaButtonOnExit", true)
private fun getPrefUserAgent(): String { private fun getPrefUserAgent(): String {
val ua = appCtx.getPrefString(PreferKey.userAgent) val ua = appCtx.getPrefString(PreferKey.userAgent)
if (ua.isNullOrBlank()) { if (ua.isNullOrBlank()) {

@ -71,5 +71,5 @@ object LocalConfig {
get() = !isLastVersion(1, "txtTocRuleVersion") get() = !isLastVersion(1, "txtTocRuleVersion")
val hasUpRssSources: Boolean val hasUpRssSources: Boolean
get() = !isLastVersion(1, "rssSourceVersion") get() = !isLastVersion(2, "rssSourceVersion")
} }

@ -133,7 +133,7 @@ class EPUBFile(var book: io.legado.app.data.entities.Book) {
/*获取当前章节文本*/ /*获取当前章节文本*/
var string = getChildChapter(chapter, chapter.url) var string = getChildChapter(chapter, chapter.url)
val childContends = appDb.epubChapterDao.get(book.bookUrl, chapter.url) val childContends = appDb.epubChapterDao.get(book.bookUrl, chapter.url)
childContends?.forEach { childContends.forEach {
string += "\n" + getChildChapter(chapter, it.href) string += "\n" + getChildChapter(chapter, it.href)
} }
return string return string

@ -47,15 +47,8 @@ object BookChapterList {
} }
var chapterData = var chapterData =
analyzeChapterList( analyzeChapterList(
scope, scope, book, baseUrl, redirectUrl, body,
book, tocRule, listRule, bookSource, log = true
baseUrl,
redirectUrl,
body,
tocRule,
listRule,
bookSource,
log = true
) )
chapterData.chapterList?.let { chapterData.chapterList?.let {
chapterList.addAll(it) chapterList.addAll(it)
@ -75,14 +68,8 @@ object BookChapterList {
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getStrResponse(bookSource.bookSourceUrl).body?.let { nextBody -> ).getStrResponse(bookSource.bookSourceUrl).body?.let { nextBody ->
chapterData = analyzeChapterList( chapterData = analyzeChapterList(
this, this, book, nextUrl, nextUrl,
book, nextBody, tocRule, listRule, bookSource
nextUrl,
nextUrl,
nextBody,
tocRule,
listRule,
bookSource
) )
nextUrl = chapterData.nextUrl.firstOrNull() ?: "" nextUrl = chapterData.nextUrl.firstOrNull() ?: ""
chapterData.chapterList?.let { chapterData.chapterList?.let {
@ -144,15 +131,8 @@ object BookChapterList {
).getStrResponse(bookSource.bookSourceUrl).body ).getStrResponse(bookSource.bookSourceUrl).body
?: throw Exception("${chapterData.nextUrl}, 下载失败") ?: throw Exception("${chapterData.nextUrl}, 下载失败")
val nextChapterData = analyzeChapterList( val nextChapterData = analyzeChapterList(
this, this, book, chapterData.nextUrl, chapterData.nextUrl,
book, nextBody, tocRule, listRule, bookSource, false
chapterData.nextUrl,
chapterData.nextUrl,
nextBody,
tocRule,
listRule,
bookSource,
false
) )
synchronized(chapterDataList) { synchronized(chapterDataList) {
val isFinished = addChapterListIsFinish( val isFinished = addChapterListIsFinish(

@ -6,8 +6,8 @@ import android.content.Intent
import android.view.KeyEvent import android.view.KeyEvent
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.help.ActivityHelp import io.legado.app.help.ActivityHelp
import io.legado.app.help.AppConfig
import io.legado.app.service.AudioPlayService import io.legado.app.service.AudioPlayService
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.AudioPlay import io.legado.app.service.help.AudioPlay
@ -15,13 +15,7 @@ import io.legado.app.service.help.ReadAloud
import io.legado.app.ui.audio.AudioPlayActivity import io.legado.app.ui.audio.AudioPlayActivity
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainActivity import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/** /**
@ -61,7 +55,7 @@ class MediaButtonReceiver : BroadcastReceiver() {
return true return true
} }
fun readAloud(context: Context) { fun readAloud(context: Context, isMediaKey: Boolean = true) {
when { when {
BaseReadAloudService.isRun -> if (BaseReadAloudService.isPlay()) { BaseReadAloudService.isRun -> if (BaseReadAloudService.isPlay()) {
ReadAloud.pause(context) ReadAloud.pause(context)
@ -79,24 +73,19 @@ class MediaButtonReceiver : BroadcastReceiver() {
postEvent(EventBus.MEDIA_BUTTON, true) postEvent(EventBus.MEDIA_BUTTON, true)
ActivityHelp.isExist(AudioPlayActivity::class.java) -> ActivityHelp.isExist(AudioPlayActivity::class.java) ->
postEvent(EventBus.MEDIA_BUTTON, true) postEvent(EventBus.MEDIA_BUTTON, true)
else -> if (context.getPrefBoolean("mediaButtonOnExit", true)) { else -> if (AppConfig.mediaButtonOnExit || !isMediaKey) {
GlobalScope.launch(Main) { appDb.bookDao.lastReadBook?.let {
val lastBook: Book? = withContext(IO) { if (!ActivityHelp.isExist(MainActivity::class.java)) {
appDb.bookDao.lastReadBook Intent(context, MainActivity::class.java).let {
}
lastBook?.let {
if (!ActivityHelp.isExist(MainActivity::class.java)) {
Intent(context, MainActivity::class.java).let {
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(it)
}
}
Intent(context, ReadBookActivity::class.java).let {
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
it.putExtra("readAloud", true)
context.startActivity(it) context.startActivity(it)
} }
} }
Intent(context, ReadBookActivity::class.java).let {
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
it.putExtra("readAloud", true)
context.startActivity(it)
}
} }
} }
} }

@ -41,8 +41,8 @@ class SharedReceiverActivity : AppCompatActivity() {
} }
} }
} }
intent.getStringExtra("action") == "mediaButton" -> { intent.getStringExtra("action") == "readAloud" -> {
MediaButtonReceiver.readAloud(appCtx) MediaButtonReceiver.readAloud(appCtx, false)
} }
} }
} }

@ -71,6 +71,9 @@ object ReadBook {
appDb.bookSourceDao.getBookSource(book.origin)?.let { appDb.bookSourceDao.getBookSource(book.origin)?.let {
bookSource = it bookSource = it
webBook = WebBook(it) webBook = WebBook(it)
if (book.getImageStyle().isNullOrBlank()) {
book.setImageStyle(it.getContentRule().imageStyle)
}
} ?: let { } ?: let {
bookSource = null bookSource = null
webBook = null webBook = null
@ -413,7 +416,7 @@ object ReadBook {
durChapterIndex -> { durChapterIndex -> {
curTextChapter = curTextChapter =
ChapterProvider.getTextChapter( ChapterProvider.getTextChapter(
book, chapter, contents, chapterSize, imageStyle book, chapter, contents, chapterSize
) )
if (upContent) callBack?.upContent(resetPageOffset = resetPageOffset) if (upContent) callBack?.upContent(resetPageOffset = resetPageOffset)
callBack?.upView() callBack?.upView()
@ -423,14 +426,14 @@ object ReadBook {
durChapterIndex - 1 -> { durChapterIndex - 1 -> {
prevTextChapter = prevTextChapter =
ChapterProvider.getTextChapter( ChapterProvider.getTextChapter(
book, chapter, contents, chapterSize, imageStyle book, chapter, contents, chapterSize
) )
if (upContent) callBack?.upContent(-1, resetPageOffset) if (upContent) callBack?.upContent(-1, resetPageOffset)
} }
durChapterIndex + 1 -> { durChapterIndex + 1 -> {
nextTextChapter = nextTextChapter =
ChapterProvider.getTextChapter( ChapterProvider.getTextChapter(
book, chapter, contents, chapterSize, imageStyle book, chapter, contents, chapterSize
) )
if (upContent) callBack?.upContent(1, resetPageOffset) if (upContent) callBack?.upContent(1, resetPageOffset)
} }
@ -444,8 +447,6 @@ object ReadBook {
} }
} }
private val imageStyle get() = webBook?.bookSource?.ruleContent?.imageStyle
fun pageAnim(): Int { fun pageAnim(): Int {
book?.let { book?.let {
return if (it.getPageAnim() < 0) return if (it.getPageAnim() < 0)

@ -27,6 +27,7 @@ import io.legado.app.help.ReadBookConfig
import io.legado.app.help.ReadTipConfig import io.legado.app.help.ReadTipConfig
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.receiver.TimeBatteryReceiver import io.legado.app.receiver.TimeBatteryReceiver
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
@ -253,6 +254,16 @@ class ReadBookActivity : ReadBookBaseActivity(),
viewModel.reverseContent(it) viewModel.reverseContent(it)
} }
R.id.menu_set_charset -> showCharsetConfig() R.id.menu_set_charset -> showCharsetConfig()
R.id.menu_image_style -> {
val imgStyles =
arrayListOf(Book.imgStyleDefault, Book.imgStyleFull, Book.imgStyleText)
selector(
R.string.image_style,
imgStyles
) { _, index ->
ReadBook.book?.setImageStyle(imgStyles[index])
}
}
R.id.menu_get_progress -> ReadBook.book?.let { R.id.menu_get_progress -> ReadBook.book?.let {
viewModel.syncBookProgress(it) { progress -> viewModel.syncBookProgress(it) { progress ->
sureSyncProgress(progress) sureSyncProgress(progress)

@ -113,19 +113,15 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
val lineTop = textLine.lineTop + relativeOffset val lineTop = textLine.lineTop + relativeOffset
val lineBase = textLine.lineBase + relativeOffset val lineBase = textLine.lineBase + relativeOffset
val lineBottom = textLine.lineBottom + relativeOffset val lineBottom = textLine.lineBottom + relativeOffset
if (textLine.isImage) { drawChars(
drawImage(canvas, textLine, lineTop, lineBottom) canvas,
} else { textLine.textChars,
drawChars( lineTop,
canvas, lineBase,
textLine.textChars, lineBottom,
lineTop, isTitle = textLine.isTitle,
lineBase, isReadAloud = textLine.isReadAloud
lineBottom, )
isTitle = textLine.isTitle,
isReadAloud = textLine.isReadAloud
)
}
} }
/** /**
@ -148,7 +144,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
textPaint.color = textPaint.color =
if (isReadAloud) context.accentColor else ReadBookConfig.textColor if (isReadAloud) context.accentColor else ReadBookConfig.textColor
textChars.forEach { textChars.forEach {
canvas.drawText(it.charData, it.start, lineBase, textPaint) if (it.isImage) {
drawImage(canvas, it, lineTop, lineBottom)
} else {
canvas.drawText(it.charData, it.start, lineBase, textPaint)
}
if (it.selected) { if (it.selected) {
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint) canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint)
} }
@ -160,18 +160,16 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
*/ */
private fun drawImage( private fun drawImage(
canvas: Canvas, canvas: Canvas,
textLine: TextLine, textChar: TextChar,
lineTop: Float, lineTop: Float,
lineBottom: Float, lineBottom: Float,
) { ) {
textLine.textChars.forEach { textChar -> ReadBook.book?.let { book ->
ReadBook.book?.let { book -> val rectF = RectF(textChar.start, lineTop, textChar.end, lineBottom)
val rectF = RectF(textChar.start, lineTop, textChar.end, lineBottom) ImageProvider.getImage(book, textPage.chapterIndex, textChar.charData, true)
ImageProvider.getImage(book, textPage.chapterIndex, textChar.charData, true) ?.let {
?.let { canvas.drawBitmap(it, null, rectF, null)
canvas.drawBitmap(it, null, rectF, null) }
}
}
} }
} }

@ -12,8 +12,8 @@ data class TextLine(
var lineBase: Float = 0f, var lineBase: Float = 0f,
var lineBottom: Float = 0f, var lineBottom: Float = 0f,
val isTitle: Boolean = false, val isTitle: Boolean = false,
val isImage: Boolean = false, var isReadAloud: Boolean = false,
var isReadAloud: Boolean = false var isImage: Boolean = false
) { ) {
val charSize: Int get() = textChars.size val charSize: Int get() = textChars.size
@ -24,10 +24,6 @@ data class TextLine(
lineBase = lineBottom - textPaint.fontMetrics.descent lineBase = lineBottom - textPaint.fontMetrics.descent
} }
fun addTextChar(charData: String, start: Float, end: Float) {
textChars.add(TextChar(charData, start = start, end = end))
}
fun getTextChar(index: Int): TextChar { fun getTextChar(index: Int): TextChar {
return textChars.getOrElse(index) { return textChars.getOrElse(index) {
textChars.last() textChars.last()

@ -72,7 +72,11 @@ data class TextPage(
val char = textLine.text[i].toString() val char = textLine.text[i].toString()
val cw = StaticLayout.getDesiredWidth(char, ChapterProvider.contentPaint) val cw = StaticLayout.getDesiredWidth(char, ChapterProvider.contentPaint)
val x1 = x + cw val x1 = x + cw
textLine.addTextChar(charData = char, start = x, end = x1) textLine.textChars.add(
TextChar(
char, start = x, end = x1
)
)
x = x1 x = x1
} }
textLines.add(textLine) textLines.add(textLine)

@ -69,6 +69,8 @@ object ChapterProvider {
@JvmStatic @JvmStatic
lateinit var contentPaint: TextPaint lateinit var contentPaint: TextPaint
private const val srcReplaceChar = "🖼"
init { init {
upStyle() upStyle()
} }
@ -81,29 +83,62 @@ object ChapterProvider {
bookChapter: BookChapter, bookChapter: BookChapter,
contents: List<String>, contents: List<String>,
chapterSize: Int, chapterSize: Int,
imageStyle: String?,
): TextChapter { ): TextChapter {
val textPages = arrayListOf<TextPage>() val textPages = arrayListOf<TextPage>()
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()
var durY = 0f var durY = 0f
textPages.add(TextPage()) textPages.add(TextPage())
contents.forEachIndexed { index, text -> contents.forEachIndexed { index, content ->
val matcher = AppPattern.imgPattern.matcher(text) if (book.getImageStyle() == Book.imgStyleText) {
if (matcher.find()) { var text = content.replace(srcReplaceChar, "")
matcher.group(1)?.let { val srcList = LinkedList<String>()
if (!book.isEpub()) { val sb = StringBuffer()
val matcher = AppPattern.imgPattern.matcher(text)
while (matcher.find()) {
matcher.group(1)?.let { it ->
val src = NetworkUtils.getAbsoluteURL(bookChapter.url, it) val src = NetworkUtils.getAbsoluteURL(bookChapter.url, it)
durY = setTypeImage( srcList.add(src)
book, bookChapter, src, durY, textPages, imageStyle ImageProvider.getImage(book, bookChapter.index, src)
) matcher.appendReplacement(sb, srcReplaceChar)
} }
} }
} else { matcher.appendTail(sb)
text = sb.toString()
val isTitle = index == 0 val isTitle = index == 0
val textPaint = if (isTitle) titlePaint else contentPaint val textPaint = if (isTitle) titlePaint else contentPaint
if (!(isTitle && ReadBookConfig.titleMode == 2)) { if (!(isTitle && ReadBookConfig.titleMode == 2)) {
durY = setTypeText(text, durY, textPages, stringBuilder, isTitle, textPaint) durY = setTypeText(
text, durY, textPages, stringBuilder,
isTitle, textPaint, srcList
)
} }
} else if (book.getImageStyle() != Book.imgStyleText) {
content.replace(AppPattern.imgPattern.toRegex(), "\n\$0\n")
.split("\n").forEach { text ->
if (text.isNotBlank()) {
val matcher = AppPattern.imgPattern.matcher(text)
if (matcher.find()) {
matcher.group(1)?.let { it ->
if (!book.isEpub()) {
val src = NetworkUtils.getAbsoluteURL(bookChapter.url, it)
durY = setTypeImage(
book, bookChapter, src,
durY, textPages, book.getImageStyle()
)
}
}
} else {
val isTitle = index == 0
val textPaint = if (isTitle) titlePaint else contentPaint
if (!(isTitle && ReadBookConfig.titleMode == 2)) {
durY = setTypeText(
text, durY, textPages,
stringBuilder, isTitle, textPaint
)
}
}
}
}
} }
} }
textPages.last().height = durY + 20.dp textPages.last().height = durY + 20.dp
@ -142,9 +177,12 @@ object ChapterProvider {
var height = it.height var height = it.height
var width = it.width var width = it.width
when (imageStyle?.toUpperCase(Locale.ROOT)) { when (imageStyle?.toUpperCase(Locale.ROOT)) {
"FULL" -> { Book.imgStyleFull -> {
width = visibleWidth width = visibleWidth
height = it.height * visibleWidth / it.width height = it.height * visibleWidth / it.width
}
Book.imgStyleText -> {
} }
else -> { else -> {
if (it.width > visibleWidth) { if (it.width > visibleWidth) {
@ -197,7 +235,8 @@ object ChapterProvider {
textPages: ArrayList<TextPage>, textPages: ArrayList<TextPage>,
stringBuilder: StringBuilder, stringBuilder: StringBuilder,
isTitle: Boolean, isTitle: Boolean,
textPaint: TextPaint textPaint: TextPaint,
srcList: LinkedList<String>? = null
): Float { ): Float {
var durY = if (isTitle) y + titleTopSpacing else y var durY = if (isTitle) y + titleTopSpacing else y
val layout = if (ReadBookConfig.useZhLayout) { val layout = if (ReadBookConfig.useZhLayout) {
@ -214,7 +253,13 @@ object ChapterProvider {
if (lineIndex == 0 && layout.lineCount > 1 && !isTitle) { if (lineIndex == 0 && layout.lineCount > 1 && !isTitle) {
//第一行 //第一行
textLine.text = words textLine.text = words
addCharsToLineFirst(textLine, words.toStringArray(), textPaint, desiredWidth) addCharsToLineFirst(
textLine,
words.toStringArray(),
textPaint,
desiredWidth,
srcList
)
} else if (lineIndex == layout.lineCount - 1) { } else if (lineIndex == layout.lineCount - 1) {
//最后一行 //最后一行
textLine.text = "$words\n" textLine.text = "$words\n"
@ -222,11 +267,18 @@ object ChapterProvider {
val x = if (isTitle && ReadBookConfig.titleMode == 1) val x = if (isTitle && ReadBookConfig.titleMode == 1)
(visibleWidth - layout.getLineWidth(lineIndex)) / 2 (visibleWidth - layout.getLineWidth(lineIndex)) / 2
else 0f else 0f
addCharsToLineLast(textLine, words.toStringArray(), textPaint, x) addCharsToLineLast(textLine, words.toStringArray(), textPaint, x, srcList)
} else { } else {
//中间行 //中间行
textLine.text = words textLine.text = words
addCharsToLineMiddle(textLine, words.toStringArray(), textPaint, desiredWidth, 0f) addCharsToLineMiddle(
textLine,
words.toStringArray(),
textPaint,
desiredWidth,
0f,
srcList
)
} }
if (durY + textPaint.textHeight > visibleHeight) { if (durY + textPaint.textHeight > visibleHeight) {
//当前页面结束,设置各种值 //当前页面结束,设置各种值
@ -257,21 +309,37 @@ object ChapterProvider {
words: Array<String>, words: Array<String>,
textPaint: TextPaint, textPaint: TextPaint,
desiredWidth: Float, desiredWidth: Float,
srcList: LinkedList<String>?
) { ) {
var x = 0f var x = 0f
if (!ReadBookConfig.textFullJustify) { if (!ReadBookConfig.textFullJustify) {
addCharsToLineLast(textLine, words, textPaint, x) addCharsToLineLast(textLine, words, textPaint, x, srcList)
return return
} }
val bodyIndent = ReadBookConfig.paragraphIndent val bodyIndent = ReadBookConfig.paragraphIndent
val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length
bodyIndent.toStringArray().forEach { bodyIndent.toStringArray().forEach {
val x1 = x + icw val x1 = x + icw
textLine.addTextChar(charData = it, start = paddingLeft + x, end = paddingLeft + x1) if (srcList != null && it == srcReplaceChar) {
textLine.textChars.add(
TextChar(
srcList.removeFirst(),
start = paddingLeft + x,
end = paddingLeft + x1,
isImage = true
)
)
} else {
textLine.textChars.add(
TextChar(
it, start = paddingLeft + x, end = paddingLeft + x1
)
)
}
x = x1 x = x1
} }
val words1 = words.copyOfRange(bodyIndent.length, words.size) val words1 = words.copyOfRange(bodyIndent.length, words.size)
addCharsToLineMiddle(textLine, words1, textPaint, desiredWidth, x) addCharsToLineMiddle(textLine, words1, textPaint, desiredWidth, x, srcList)
} }
/** /**
@ -283,9 +351,10 @@ object ChapterProvider {
textPaint: TextPaint, textPaint: TextPaint,
desiredWidth: Float, desiredWidth: Float,
startX: Float, startX: Float,
srcList: LinkedList<String>?
) { ) {
if (!ReadBookConfig.textFullJustify) { if (!ReadBookConfig.textFullJustify) {
addCharsToLineLast(textLine, words, textPaint, startX) addCharsToLineLast(textLine, words, textPaint, startX, srcList)
return return
} }
val gapCount: Int = words.lastIndex val gapCount: Int = words.lastIndex
@ -294,7 +363,22 @@ object ChapterProvider {
words.forEachIndexed { index, s -> words.forEachIndexed { index, s ->
val cw = StaticLayout.getDesiredWidth(s, textPaint) val cw = StaticLayout.getDesiredWidth(s, textPaint)
val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw) val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw)
textLine.addTextChar(charData = s, start = paddingLeft + x, end = paddingLeft + x1) if (srcList != null && s == srcReplaceChar) {
textLine.textChars.add(
TextChar(
srcList.removeFirst(),
start = paddingLeft + x,
end = paddingLeft + x1,
isImage = true
)
)
} else {
textLine.textChars.add(
TextChar(
s, start = paddingLeft + x, end = paddingLeft + x1
)
)
}
x = x1 x = x1
} }
exceed(textLine, words) exceed(textLine, words)
@ -308,12 +392,28 @@ object ChapterProvider {
words: Array<String>, words: Array<String>,
textPaint: TextPaint, textPaint: TextPaint,
startX: Float, startX: Float,
srcList: LinkedList<String>?
) { ) {
var x = startX var x = startX
words.forEach { words.forEach {
val cw = StaticLayout.getDesiredWidth(it, textPaint) val cw = StaticLayout.getDesiredWidth(it, textPaint)
val x1 = x + cw val x1 = x + cw
textLine.addTextChar(charData = it, start = paddingLeft + x, end = paddingLeft + x1) if (srcList != null && it == srcReplaceChar) {
textLine.textChars.add(
TextChar(
srcList.removeFirst(),
start = paddingLeft + x,
end = paddingLeft + x1,
isImage = true
)
)
} else {
textLine.textChars.add(
TextChar(
it, start = paddingLeft + x, end = paddingLeft + x1
)
)
}
x = x1 x = x1
} }
exceed(textLine, words) exceed(textLine, words)

@ -7,21 +7,19 @@ import android.text.TextPaint
import io.legado.app.utils.toStringArray import io.legado.app.utils.toStringArray
import kotlin.math.max import kotlin.math.max
/* /**
* 针对中文的断行排版处理-by hoodie13 * 针对中文的断行排版处理-by hoodie13
* 因为StaticLayout对标点处理不符合国人习惯继承Layout * 因为StaticLayout对标点处理不符合国人习惯继承Layout
* 接口封的不抽象数组用的也琐碎因目前语法不熟悉后面完善 * */
* */
@Suppress("MemberVisibilityCanBePrivate", "unused") @Suppress("MemberVisibilityCanBePrivate", "unused")
class ZhLayout( class ZhLayout(
text: String, text: String,
textPaint: TextPaint, textPaint: TextPaint,
width: Int width: Int
) : Layout(text, textPaint, width, Alignment.ALIGN_NORMAL, 0f, 0f) { ) : Layout(text, textPaint, width, Alignment.ALIGN_NORMAL, 0f, 0f) {
var lineStart = IntArray(1000) private val defaultCapacity = 10
var lineEnd = IntArray(1000) var lineStart = IntArray(defaultCapacity)
var lineWidth = FloatArray(1000) var lineWidth = FloatArray(defaultCapacity)
var lineCompressMod = IntArray(1000)
private var lineCount = 0 private var lineCount = 0
private val curPaint = textPaint private val curPaint = textPaint
private val cnCharWitch = getDesiredWidth("", textPaint) private val cnCharWitch = getDesiredWidth("", textPaint)
@ -103,38 +101,32 @@ class ZhLayout(
when (breakMod) { when (breakMod) {
BreakMod.NORMAL -> {//模式0 正常断行 BreakMod.NORMAL -> {//模式0 正常断行
offset = cw offset = cw
lineEnd[line] = index lineStart[line + 1] = index
lineCompressMod[line] = 0
breakCharCnt = 1 breakCharCnt = 1
} }
BreakMod.BREAK_ONE_CHAR -> {//模式1 当前行下移一个字 BreakMod.BREAK_ONE_CHAR -> {//模式1 当前行下移一个字
offset = cw + cwPre offset = cw + cwPre
lineEnd[line] = index - 1 lineStart[line + 1] = index - 1
lineCompressMod[line] = 0
breakCharCnt = 2 breakCharCnt = 2
} }
BreakMod.BREAK_MORE_CHAR -> {//模式2 当前行下移多个字 BreakMod.BREAK_MORE_CHAR -> {//模式2 当前行下移多个字
offset = cw + cwPre offset = cw + cwPre
lineEnd[line] = index - breakIndex lineStart[line + 1] = index - breakIndex
lineCompressMod[line] = 0
breakCharCnt = breakIndex + 1 breakCharCnt = breakIndex + 1
} }
BreakMod.CPS_1 -> {//模式3 两个后置标点压缩 BreakMod.CPS_1 -> {//模式3 两个后置标点压缩
offset = 0f offset = 0f
lineEnd[line] = index + 1 lineStart[line + 1] = index + 1
lineCompressMod[line] = 1
breakCharCnt = 0 breakCharCnt = 0
} }
BreakMod.CPS_2 -> { //模式4 前置标点压缩+前置标点压缩+字 BreakMod.CPS_2 -> { //模式4 前置标点压缩+前置标点压缩+字
offset = 0f offset = 0f
lineEnd[line] = index + 1 lineStart[line + 1] = index + 1
lineCompressMod[line] = 2
breakCharCnt = 0 breakCharCnt = 0
} }
BreakMod.CPS_3 -> {//模式5 前置标点压缩+字+后置标点压缩 BreakMod.CPS_3 -> {//模式5 前置标点压缩+字+后置标点压缩
offset = 0f offset = 0f
lineEnd[line] = index + 1 lineStart[line + 1] = index + 1
lineCompressMod[line] = 3
breakCharCnt = 0 breakCharCnt = 0
} }
} }
@ -144,38 +136,39 @@ class ZhLayout(
/*当前行写满情况下的断行*/ /*当前行写满情况下的断行*/
if (breakLine) { if (breakLine) {
lineWidth[line] = lineW - offset lineWidth[line] = lineW - offset
lineStart[line + 1] = lineEnd[line]
lineW = offset lineW = offset
line++ addLineArray(++line)
} }
/*已到最后一个字符*/ /*已到最后一个字符*/
if ((words.lastIndex) == index) { if ((words.lastIndex) == index) {
if (!breakLine) { if (!breakLine) {
offset = 0f offset = 0f
lineEnd[line] = index + 1 lineStart[line + 1] = index + 1
lineStart[line + 1] = lineEnd[line]
lineWidth[line] = lineW - offset lineWidth[line] = lineW - offset
lineW = offset lineW = offset
line++ addLineArray(++line)
} }
/*写满断行、段落末尾、且需要下移字符,这种特殊情况下要额外多一行*/ /*写满断行、段落末尾、且需要下移字符,这种特殊情况下要额外多一行*/
else if (breakCharCnt > 0) { else if (breakCharCnt > 0) {
lineEnd[line] = lineStart[line] + breakCharCnt lineStart[line + 1] = lineStart[line] + breakCharCnt
lineStart[line + 1] = lineEnd[line]
lineWidth[line] = lineW lineWidth[line] = lineW
line++ addLineArray(++line)
} }
} }
cwPre = cw cwPre = cw
if (line >= 999) {
return@forEachIndexed
}
} }
lineCount = line lineCount = line
} }
private fun addLineArray(line: Int) {
if (lineStart.size <= line + 1) {
lineStart = lineStart.copyOf(line + defaultCapacity)
lineWidth = lineWidth.copyOf(line + defaultCapacity)
}
}
private fun isPostPanc(string: String): Boolean { private fun isPostPanc(string: String): Boolean {
val panc = arrayOf( val panc = arrayOf(
"", "", "", "", "", "", "", "", "", "", "}", "", "", "", "", "", "", "", "", "", "", "}",
@ -251,7 +244,9 @@ class ZhLayout(
return 0 return 0
} }
override fun getLineWidth(line: Int): Float = lineWidth[line] override fun getLineWidth(line: Int): Float {
return lineWidth[line]
}
override fun getEllipsisStart(line: Int): Int { override fun getEllipsisStart(line: Int): Int {
return 0 return 0
@ -261,101 +256,4 @@ class ZhLayout(
return 0 return 0
} }
fun getDefaultWidth(): Float = cnCharWitch
/*
* @fun获取当前行的平均间隔用于两端对齐获取左对齐时的右边间隔用于间隔过大时不再两端对齐
* @in当前字符串最大显示宽度
* @out单个字符的平均间隔左对齐的最大间隔
*/
fun getInterval(line: Int, words: Array<String>, visibleWidth: Int): Interval {
val interval = Interval()
val total: Float
val d: Float
val lastIndex = words.lastIndex
val desiredWidth = getLineWidth(line)
if (lineCompressMod[line] > 0) {
val gapCount: Int = lastIndex - 1
val lastWordsWith = getDesiredWidth(words[lastIndex], curPaint)
total = visibleWidth - desiredWidth + lastWordsWith
d = total / gapCount
} else {
val gapCount: Int = lastIndex
total = visibleWidth - desiredWidth
d = total / gapCount
}
interval.total = total
interval.single = d
return interval
}
/*
* @fun获取当前行不同字符的位置
* @in当前字符对于最后一个字符的偏移值字符间隔定位参数
* @out定位参数
*/
fun getLocate(line: Int, idx: Int, string: String, interval: Float, locate: Locate) {
val cw = getDesiredWidth(string, curPaint)
when (lineCompressMod[line]) {
1 -> {
when (idx) {
1 -> {
val offset = getPostPancOffset(string)
locate.start -= offset
locate.end = locate.start + cw / 2 + offset
}
0 -> {
locate.start -= getPostPancOffset(string)
locate.end = locate.start + cw
}
else -> {
locate.end = locate.start + cw + interval
}
}
}
2 -> {
when (idx) {
2 -> {
val offset = getPostPancOffset(string)
locate.start -= offset
locate.end = locate.start + cw / 2 + offset
}
1 -> {
val offset = getPostPancOffset(string)
locate.start -= offset
locate.end = locate.start + cw / 2 + offset
}
0 -> {
locate.end = locate.start + cw
}
else -> {
locate.end = locate.start + cw + interval
}
}
}
3 -> {
when (idx) {
2 -> {
val offset = getPrePancOffset(string)
locate.start -= offset
locate.end = locate.start + cw / 2 + offset
}
1 -> {
locate.end = locate.start + cw + interval
}
0 -> {
locate.start -= getPostPancOffset(string)
locate.end = locate.start + cw
}
else -> {
locate.end = locate.start + cw + interval
}
}
}
else -> {
locate.end = if (idx != 0) (locate.start + cw + interval) else (locate.start + cw)
}
}
}
} }

@ -25,6 +25,7 @@ import io.legado.app.ui.rss.source.manage.RssSourceActivity
import io.legado.app.ui.rss.source.manage.RssSourceViewModel import io.legado.app.ui.rss.source.manage.RssSourceViewModel
import io.legado.app.ui.rss.subscription.RuleSubActivity import io.legado.app.ui.rss.subscription.RuleSubActivity
import io.legado.app.utils.cnCompare import io.legado.app.utils.cnCompare
import io.legado.app.utils.openUrl
import io.legado.app.utils.splitNotBlank import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
@ -143,9 +144,13 @@ class RssFragment : VMBaseFragment<RssSourceViewModel>(R.layout.fragment_rss),
override fun openRss(rssSource: RssSource) { override fun openRss(rssSource: RssSource) {
if (rssSource.singleUrl) { if (rssSource.singleUrl) {
startActivity<ReadRssActivity> { if (rssSource.sourceUrl.startsWith("http", true)) {
putExtra("title", rssSource.sourceName) startActivity<ReadRssActivity> {
putExtra("origin", rssSource.sourceUrl) putExtra("title", rssSource.sourceName)
putExtra("origin", rssSource.sourceUrl)
}
} else {
context?.openUrl(rssSource.sourceUrl)
} }
} else { } else {
startActivity<RssSortActivity> { startActivity<RssSortActivity> {

@ -9,7 +9,6 @@ import java.io.File
import java.util.* import java.util.*
val removeHtmlRegex = "</?(?:div|p|br|hr|h\\d|article|dd|dl)[^>]*>".toRegex() val removeHtmlRegex = "</?(?:div|p|br|hr|h\\d|article|dd|dl)[^>]*>".toRegex()
val imgRegex = "<img[^>]*>".toRegex()
val notImgHtmlRegex = "</?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>".toRegex() val notImgHtmlRegex = "</?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim() fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()
@ -54,7 +53,6 @@ fun String?.isJsonArray(): Boolean =
fun String?.htmlFormat(): String { fun String?.htmlFormat(): String {
this ?: return "" this ?: return ""
return this return this
.replace(imgRegex, "\n$0\n")
.replace(removeHtmlRegex, "\n") .replace(removeHtmlRegex, "\n")
.replace(notImgHtmlRegex, "") .replace(notImgHtmlRegex, "")
.replace("\\s*\\n+\\s*".toRegex(), "\n  ") .replace("\\s*\\n+\\s*".toRegex(), "\n  ")

@ -92,6 +92,11 @@
android:checked="false" android:checked="false"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/menu_image_style"
android:title="@string/image_style"
app:showAsAction="never" />
<item <item
android:id="@+id/menu_update_toc" android:id="@+id/menu_update_toc"
android:title="@string/update_toc" android:title="@string/update_toc"

@ -808,5 +808,6 @@
<string name="debug">调试</string> <string name="debug">调试</string>
<string name="crash_log">崩溃日志</string> <string name="crash_log">崩溃日志</string>
<string name="use_zh_layout">使用自定义中文分行</string> <string name="use_zh_layout">使用自定义中文分行</string>
<string name="image_style">图片样式</string>
</resources> </resources>

@ -810,5 +810,6 @@
<string name="export_to_web_dav">匯出到WebDav</string> <string name="export_to_web_dav">匯出到WebDav</string>
<string name="reverse_content">反转内容</string> <string name="reverse_content">反转内容</string>
<string name="debug">调试</string> <string name="debug">调试</string>
<string name="image_style">图片样式</string>
</resources> </resources>

@ -812,5 +812,6 @@
<string name="debug">调试</string> <string name="debug">调试</string>
<string name="crash_log">崩溃日志</string> <string name="crash_log">崩溃日志</string>
<string name="use_zh_layout">使用自定义中文分行</string> <string name="use_zh_layout">使用自定义中文分行</string>
<string name="image_style">图片样式</string>
</resources> </resources>

@ -815,5 +815,6 @@
<string name="debug">调试</string> <string name="debug">调试</string>
<string name="crash_log">崩溃日志</string> <string name="crash_log">崩溃日志</string>
<string name="use_zh_layout">使用自定义中文分行</string> <string name="use_zh_layout">使用自定义中文分行</string>
<string name="image_style">图片样式</string>
</resources> </resources>

@ -36,7 +36,7 @@
android:targetClass="io.legado.app.receiver.SharedReceiverActivity"> android:targetClass="io.legado.app.receiver.SharedReceiverActivity">
<extra <extra
android:name="action" android:name="action"
android:value="mediaButton" /> android:value="readAloud" />
</intent> </intent>
<categories android:name="android.shortcut.conversation" /> <categories android:name="android.shortcut.conversation" />
</shortcut> </shortcut>

@ -0,0 +1 @@
emulator -avd android11 -dns-server 8.8.8.8 -no-snapshot-load
Loading…
Cancel
Save