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. 14
      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. 19
      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. 12
      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. 132
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
  17. 152
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt
  18. 5
      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": "阅读下载",
"sourceUrl": "https:\/\/github.com\/gedoor\/legado\/releases",
"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)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2021/03/01**
**2021/03/08**
* 阅读页面停留10分钟之后自动备份进度
* 添加了针对中文的断行排版处理-by hoodie13, 需要再阅读界面设置里手动开启
* 添加朗读快捷方式
* 优化Epub解析 by hoodie13
* epub书籍增加cache by hoodie13
* 修复切换书籍或者章节时的断言崩溃问题。看漫画容易复现。 by hoodie13
* 修正增加书签alert的正文内容较多时,确定键溢出屏幕问题 by hoodie13
* 图片样式添加TEXT, 阅读界面菜单里可以选择图片样式
**2021/02/26**
* 添加反转内容功能

@ -12,7 +12,7 @@ interface EpubChapterDao {
fun getCnt(bookUrl: String): Int
@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)
fun insert(vararg chapter: EpubChapter)

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

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

@ -71,5 +71,5 @@ object LocalConfig {
get() = !isLastVersion(1, "txtTocRuleVersion")
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)
val childContends = appDb.epubChapterDao.get(book.bookUrl, chapter.url)
childContends?.forEach {
childContends.forEach {
string += "\n" + getChildChapter(chapter, it.href)
}
return string

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

@ -6,8 +6,8 @@ import android.content.Intent
import android.view.KeyEvent
import io.legado.app.constant.EventBus
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.help.ActivityHelp
import io.legado.app.help.AppConfig
import io.legado.app.service.AudioPlayService
import io.legado.app.service.BaseReadAloudService
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.book.read.ReadBookActivity
import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.getPrefBoolean
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
}
fun readAloud(context: Context) {
fun readAloud(context: Context, isMediaKey: Boolean = true) {
when {
BaseReadAloudService.isRun -> if (BaseReadAloudService.isPlay()) {
ReadAloud.pause(context)
@ -79,12 +73,8 @@ class MediaButtonReceiver : BroadcastReceiver() {
postEvent(EventBus.MEDIA_BUTTON, true)
ActivityHelp.isExist(AudioPlayActivity::class.java) ->
postEvent(EventBus.MEDIA_BUTTON, true)
else -> if (context.getPrefBoolean("mediaButtonOnExit", true)) {
GlobalScope.launch(Main) {
val lastBook: Book? = withContext(IO) {
appDb.bookDao.lastReadBook
}
lastBook?.let {
else -> if (AppConfig.mediaButtonOnExit || !isMediaKey) {
appDb.bookDao.lastReadBook?.let {
if (!ActivityHelp.isExist(MainActivity::class.java)) {
Intent(context, MainActivity::class.java).let {
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@ -101,6 +91,5 @@ class MediaButtonReceiver : BroadcastReceiver() {
}
}
}
}
}

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

@ -71,6 +71,9 @@ object ReadBook {
appDb.bookSourceDao.getBookSource(book.origin)?.let {
bookSource = it
webBook = WebBook(it)
if (book.getImageStyle().isNullOrBlank()) {
book.setImageStyle(it.getContentRule().imageStyle)
}
} ?: let {
bookSource = null
webBook = null
@ -413,7 +416,7 @@ object ReadBook {
durChapterIndex -> {
curTextChapter =
ChapterProvider.getTextChapter(
book, chapter, contents, chapterSize, imageStyle
book, chapter, contents, chapterSize
)
if (upContent) callBack?.upContent(resetPageOffset = resetPageOffset)
callBack?.upView()
@ -423,14 +426,14 @@ object ReadBook {
durChapterIndex - 1 -> {
prevTextChapter =
ChapterProvider.getTextChapter(
book, chapter, contents, chapterSize, imageStyle
book, chapter, contents, chapterSize
)
if (upContent) callBack?.upContent(-1, resetPageOffset)
}
durChapterIndex + 1 -> {
nextTextChapter =
ChapterProvider.getTextChapter(
book, chapter, contents, chapterSize, imageStyle
book, chapter, contents, chapterSize
)
if (upContent) callBack?.upContent(1, resetPageOffset)
}
@ -444,8 +447,6 @@ object ReadBook {
}
}
private val imageStyle get() = webBook?.bookSource?.ruleContent?.imageStyle
fun pageAnim(): Int {
book?.let {
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.storage.Backup
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.receiver.TimeBatteryReceiver
import io.legado.app.service.BaseReadAloudService
@ -253,6 +254,16 @@ class ReadBookActivity : ReadBookBaseActivity(),
viewModel.reverseContent(it)
}
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 {
viewModel.syncBookProgress(it) { progress ->
sureSyncProgress(progress)

@ -113,9 +113,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
val lineTop = textLine.lineTop + relativeOffset
val lineBase = textLine.lineBase + relativeOffset
val lineBottom = textLine.lineBottom + relativeOffset
if (textLine.isImage) {
drawImage(canvas, textLine, lineTop, lineBottom)
} else {
drawChars(
canvas,
textLine.textChars,
@ -126,7 +123,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
isReadAloud = textLine.isReadAloud
)
}
}
/**
* 绘制文字
@ -148,7 +144,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
textPaint.color =
if (isReadAloud) context.accentColor else ReadBookConfig.textColor
textChars.forEach {
if (it.isImage) {
drawImage(canvas, it, lineTop, lineBottom)
} else {
canvas.drawText(it.charData, it.start, lineBase, textPaint)
}
if (it.selected) {
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint)
}
@ -160,11 +160,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
*/
private fun drawImage(
canvas: Canvas,
textLine: TextLine,
textChar: TextChar,
lineTop: Float,
lineBottom: Float,
) {
textLine.textChars.forEach { textChar ->
ReadBook.book?.let { book ->
val rectF = RectF(textChar.start, lineTop, textChar.end, lineBottom)
ImageProvider.getImage(book, textPage.chapterIndex, textChar.charData, true)
@ -173,7 +172,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
}
}
}
}
/**
* 滚动事件

@ -12,8 +12,8 @@ data class TextLine(
var lineBase: Float = 0f,
var lineBottom: Float = 0f,
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
@ -24,10 +24,6 @@ data class TextLine(
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 {
return textChars.getOrElse(index) {
textChars.last()

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

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

@ -7,10 +7,9 @@ import android.text.TextPaint
import io.legado.app.utils.toStringArray
import kotlin.math.max
/*
/**
* 针对中文的断行排版处理-by hoodie13
* 因为StaticLayout对标点处理不符合国人习惯继承Layout
* 接口封的不抽象数组用的也琐碎因目前语法不熟悉后面完善
* */
@Suppress("MemberVisibilityCanBePrivate", "unused")
class ZhLayout(
@ -18,10 +17,9 @@ class ZhLayout(
textPaint: TextPaint,
width: Int
) : Layout(text, textPaint, width, Alignment.ALIGN_NORMAL, 0f, 0f) {
var lineStart = IntArray(1000)
var lineEnd = IntArray(1000)
var lineWidth = FloatArray(1000)
var lineCompressMod = IntArray(1000)
private val defaultCapacity = 10
var lineStart = IntArray(defaultCapacity)
var lineWidth = FloatArray(defaultCapacity)
private var lineCount = 0
private val curPaint = textPaint
private val cnCharWitch = getDesiredWidth("", textPaint)
@ -103,38 +101,32 @@ class ZhLayout(
when (breakMod) {
BreakMod.NORMAL -> {//模式0 正常断行
offset = cw
lineEnd[line] = index
lineCompressMod[line] = 0
lineStart[line + 1] = index
breakCharCnt = 1
}
BreakMod.BREAK_ONE_CHAR -> {//模式1 当前行下移一个字
offset = cw + cwPre
lineEnd[line] = index - 1
lineCompressMod[line] = 0
lineStart[line + 1] = index - 1
breakCharCnt = 2
}
BreakMod.BREAK_MORE_CHAR -> {//模式2 当前行下移多个字
offset = cw + cwPre
lineEnd[line] = index - breakIndex
lineCompressMod[line] = 0
lineStart[line + 1] = index - breakIndex
breakCharCnt = breakIndex + 1
}
BreakMod.CPS_1 -> {//模式3 两个后置标点压缩
offset = 0f
lineEnd[line] = index + 1
lineCompressMod[line] = 1
lineStart[line + 1] = index + 1
breakCharCnt = 0
}
BreakMod.CPS_2 -> { //模式4 前置标点压缩+前置标点压缩+字
offset = 0f
lineEnd[line] = index + 1
lineCompressMod[line] = 2
lineStart[line + 1] = index + 1
breakCharCnt = 0
}
BreakMod.CPS_3 -> {//模式5 前置标点压缩+字+后置标点压缩
offset = 0f
lineEnd[line] = index + 1
lineCompressMod[line] = 3
lineStart[line + 1] = index + 1
breakCharCnt = 0
}
}
@ -144,38 +136,39 @@ class ZhLayout(
/*当前行写满情况下的断行*/
if (breakLine) {
lineWidth[line] = lineW - offset
lineStart[line + 1] = lineEnd[line]
lineW = offset
line++
addLineArray(++line)
}
/*已到最后一个字符*/
if ((words.lastIndex) == index) {
if (!breakLine) {
offset = 0f
lineEnd[line] = index + 1
lineStart[line + 1] = lineEnd[line]
lineStart[line + 1] = index + 1
lineWidth[line] = lineW - offset
lineW = offset
line++
addLineArray(++line)
}
/*写满断行、段落末尾、且需要下移字符,这种特殊情况下要额外多一行*/
else if (breakCharCnt > 0) {
lineEnd[line] = lineStart[line] + breakCharCnt
lineStart[line + 1] = lineEnd[line]
lineStart[line + 1] = lineStart[line] + breakCharCnt
lineWidth[line] = lineW
line++
addLineArray(++line)
}
}
cwPre = cw
if (line >= 999) {
return@forEachIndexed
}
}
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 {
val panc = arrayOf(
"", "", "", "", "", "", "", "", "", "", "}",
@ -251,7 +244,9 @@ class ZhLayout(
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 {
return 0
@ -261,101 +256,4 @@ class ZhLayout(
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.subscription.RuleSubActivity
import io.legado.app.utils.cnCompare
import io.legado.app.utils.openUrl
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.startActivity
import io.legado.app.utils.viewbindingdelegate.viewBinding
@ -143,10 +144,14 @@ class RssFragment : VMBaseFragment<RssSourceViewModel>(R.layout.fragment_rss),
override fun openRss(rssSource: RssSource) {
if (rssSource.singleUrl) {
if (rssSource.sourceUrl.startsWith("http", true)) {
startActivity<ReadRssActivity> {
putExtra("title", rssSource.sourceName)
putExtra("origin", rssSource.sourceUrl)
}
} else {
context?.openUrl(rssSource.sourceUrl)
}
} else {
startActivity<RssSortActivity> {
putExtra("url", rssSource.sourceUrl)

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

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

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

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

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

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

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

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