Compare commits

...

7 Commits

Author SHA1 Message Date
Xwite
988a1b5385 Bump cronet from 123.0.6312.80 to 124.0.6367.82
- Changes in the [Git log](https://chromium.googlesource.com/chromium/src/+log/123.0.6312.80..124.0.6367.82)
2024-04-29 01:28:47 +00:00
Horis
94fb7564f1 优化 2024-04-28 16:41:45 +08:00
Horis
11eb304fae 优化 2024-04-28 16:39:48 +08:00
Horis
33e9286a8e 优化 2024-04-28 12:56:35 +08:00
Horis
e57ad3a09e 优化 2024-04-28 12:14:13 +08:00
Horis
f83a2bc357 优化 2024-04-27 22:28:44 +08:00
Antecer
cb06d149f7 修复ttf字体轮廓查询不到的bug 2024-04-27 12:49:11 +08:00
18 changed files with 184 additions and 142 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1 +1 @@
{"arm64-v8a":"bbf2c50d1ebf0763d451b08e290a3244","armeabi-v7a":"ecb872f7b1b5342f4d7c36262bec0600","x86":"4da4832b89e2412d808c5b1ecdc24e3d","x86_64":"62080f051db02ed0e939affb39ce67fb","version":"123.0.6312.80"}
{"x86":"cb929a66b3c21a5b27b49f6ff79b5441","armeabi-v7a":"4b5b7c7be1d4cab7f848d982081062f6","x86_64":"698858d140e577226364d7310ea015fb","arm64-v8a":"d03030209bb386fcf6842b98e93a9942","version":"124.0.6367.82"}

View File

@ -13,6 +13,7 @@
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
**2024/02/27**
* 更新cronet: 124.0.6367.82
* 更新cronet: 123.0.6312.80
* 更新cronet: 123.0.6312.40

View File

@ -178,7 +178,8 @@ let options = {
if(b64){
var f1 = java.queryBase64TTF(b64[1]);
var f2 = java.queryTTF("https://alanskycn.gitee.io/teachme/assets/font/Source Han Sans CN Regular.ttf");
return java.replaceFont(result, f1, f2);
// return java.replaceFont(result, f1, f2);
return java.replaceFont(result, f1, f2, true); // 过滤掉f1中不存在的字形
}
return result;
})()

View File

@ -799,23 +799,34 @@ interface JsExtensions : JsEncodeUtils {
fun replaceFont(
text: String,
errorQueryTTF: QueryTTF?,
correctQueryTTF: QueryTTF?
correctQueryTTF: QueryTTF?,
filter: Boolean
): String {
if (errorQueryTTF == null || correctQueryTTF == null) return text
val contentArray = text.toStringArray() //这里不能用toCharArray,因为有些文字占多个字节
contentArray.forEachIndexed { index, s ->
val oldCode = s.codePointAt(0)
if (errorQueryTTF.inLimit(oldCode)) {
val glyf = errorQueryTTF.getGlyfByCode(oldCode)
val code = correctQueryTTF.getCodeByGlyf(glyf)
if (code != 0) {
contentArray[index] = code.toChar().toString()
}
val glyf = errorQueryTTF.getGlyfByCode(oldCode)
val code = correctQueryTTF.getCodeByGlyf(glyf)
if (code != 0) {
contentArray[index] = code.toChar().toString()
}
if (glyf == "" && filter) {
// 删除轮廓数据为空的字符
contentArray[index] = ""
}
}
return contentArray.joinToString("")
}
fun replaceFont(
text: String,
errorQueryTTF: QueryTTF?,
correctQueryTTF: QueryTTF?
): String {
return replaceFont(text, errorQueryTTF, correctQueryTTF, false)
}
/**
* 章节数转数字

View File

@ -11,7 +11,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@SuppressWarnings({"FieldCanBeLocal", "StatementWithEmptyBody", "unused"})
@SuppressWarnings({"FieldCanBeLocal", "unused"})
public class QueryTTF {
private static class Header {
public int majorVersion;
@ -222,7 +222,7 @@ public class QueryTTF {
private final MaxpLayout maxp = new MaxpLayout();
private final List<Integer> loca = new LinkedList<>();
private final CmapLayout Cmap = new CmapLayout();
private final List<GlyfLayout> glyf = new LinkedList<>();
private final List<String> glyf = new LinkedList<>();
@SuppressWarnings("unchecked")
private final Pair<Integer, Integer>[] pps = new Pair[]{
Pair.of(3, 10),
@ -233,10 +233,9 @@ public class QueryTTF {
Pair.of(0, 1)
};
public final Map<Integer, String> codeToGlyph = new HashMap<>();
public final Map<String, Integer> glyphToCode = new HashMap<>();
private int limitMix = Integer.MAX_VALUE;
private int limitMax = 0;
public final Map<Integer, String> unicodeToGlyph = new HashMap<>();
public final Map<String, Integer> glyphToUnicode = new HashMap<>();
public final Map<Integer, Integer> unicodeToGlyphIndex = new HashMap<>();
/**
* 构造函数
@ -406,83 +405,42 @@ public class QueryTTF {
}
}
}
// 解析 glyf (字体轮廓数据表)
// 读取 glyf (字体轮廓数据表)
for (Directory Temp : directorys) {
if (Temp.tag.equals("glyf")) {
fontReader.index = Temp.offset;
for (int i = 0; i < maxp.numGlyphs; ++i) {
int glyfCount = maxp.numGlyphs;
for (int i = 0; i < glyfCount; ) {
fontReader.index = Temp.offset + loca.get(i);
++i;
int glyfNextIndex = i < glyfCount ? loca.get(i) : Temp.length;
byte[] glyph;
short numberOfContours = fontReader.ReadInt16();
if (numberOfContours > 0) {
GlyfLayout g = new GlyfLayout();
g.numberOfContours = numberOfContours;
g.xMin = fontReader.ReadInt16();
g.yMin = fontReader.ReadInt16();
g.xMax = fontReader.ReadInt16();
g.yMax = fontReader.ReadInt16();
g.endPtsOfContours = fontReader.GetUInt16Array(numberOfContours);
g.instructionLength = fontReader.ReadUInt16();
g.instructions = fontReader.GetBytes(g.instructionLength);
int flagLength = g.endPtsOfContours[g.endPtsOfContours.length - 1] + 1;
// 获取轮廓点描述标志
g.flags = new byte[flagLength];
for (int n = 0; n < flagLength; ++n) {
g.flags[n] = fontReader.GetByte();
if ((g.flags[n] & 0x08) != 0x00) {
for (int m = fontReader.ReadUInt8(); m > 0; --m) {
g.flags[++n] = g.flags[n - 1];
}
}
}
// 获取轮廓点描述x轴相对值
g.xCoordinates = new short[flagLength];
for (int n = 0; n < flagLength; ++n) {
short same = (short) ((g.flags[n] & 0x10) != 0 ? 1 : -1);
if ((g.flags[n] & 0x02) != 0) {
g.xCoordinates[n] = (short) (same * fontReader.ReadUInt8());
} else {
g.xCoordinates[n] = same == 1 ? (short) 0 : fontReader.ReadInt16();
}
}
// 获取轮廓点描述y轴相对值
g.yCoordinates = new short[flagLength];
for (int n = 0; n < flagLength; ++n) {
short same = (short) ((g.flags[n] & 0x20) != 0 ? 1 : -1);
if ((g.flags[n] & 0x04) != 0) {
g.yCoordinates[n] = (short) (same * fontReader.ReadUInt8());
} else {
g.yCoordinates[n] = same == 1 ? (short) 0 : fontReader.ReadInt16();
}
}
/* 相对坐标转绝对坐标
for (int n = 1; n < flagLength; ++n) {
xCoordinates[n] += xCoordinates[n - 1];
yCoordinates[n] += yCoordinates[n - 1];
}*/
glyf.add(g);
short g_xMin = fontReader.ReadInt16();
short g_yMin = fontReader.ReadInt16();
short g_xMax = fontReader.ReadInt16();
short g_yMax = fontReader.ReadInt16();
int[] endPtsOfContours = fontReader.GetUInt16Array(numberOfContours);
glyph = fontReader.GetBytes(glyfNextIndex - (fontReader.index - Temp.offset));
} else {
// 复合字体暂未使用
glyph = fontReader.GetBytes(glyfNextIndex - (fontReader.index - 2));
}
glyf.add(getHexFromBytes(glyph));
}
}
}
// 建立Unicode&Glyph双向表
for (int key = 0; key < 130000; ++key) {
if (key == 0xFF) key = 0x3400;
int gid = getGlyfIndex(key);
if (gid == 0 || gid >= glyf.size()) continue;
StringBuilder sb = new StringBuilder();
// 字型数据转String方便存HashMap
for (short b : glyf.get(gid).xCoordinates) sb.append(b);
for (short b : glyf.get(gid).yCoordinates) sb.append(b);
String val = sb.toString();
if (limitMix > key) limitMix = key;
if (limitMax < key) limitMax = key;
codeToGlyph.put(key, val);
if (glyphToCode.containsKey(val)) continue;
glyphToCode.put(val, key);
// if (key == 0xFF) key = 0x3400;
int gid = queryGlyfIndex(key);
if (gid >= glyf.size()) continue;
unicodeToGlyphIndex.put(key, gid);
var val = glyf.get(gid);
unicodeToGlyph.put(key, val);
if (glyphToUnicode.containsKey(val)) continue;
glyphToUnicode.put(val, key);
}
}
@ -509,11 +467,11 @@ public class QueryTTF {
/**
* 使用Unicode值查找轮廓索引
*
* @param code 传入Unicode十进制
* @param unicode 传入Unicode
* @return 返回十进制轮廓索引
*/
private int getGlyfIndex(int code) {
if (code == 0) return 0;
public int queryGlyfIndex(int unicode) {
if (unicode == 0) return 0;
int fmtKey = 0;
for (Pair<Integer, Integer> item : pps) {
for (CmapRecord record : Cmap.records) {
@ -531,34 +489,33 @@ public class QueryTTF {
assert table != null;
int fmt = table.format;
if (fmt == 0) {
if (code < table.glyphIdArray.length) glyfID = table.glyphIdArray[code] & 0xFF;
if (unicode < table.glyphIdArray.length) glyfID = table.glyphIdArray[unicode] & 0xFF;
} else if (fmt == 4) {
CmapFormat4 tab = (CmapFormat4) table;
if (code > tab.endCode[tab.endCode.length - 1]) return 0;
if (unicode > tab.endCode[tab.endCode.length - 1]) return 0;
// 二分法查找数值索引
int start = 0, middle, end = tab.endCode.length - 1;
while (start + 1 < end) {
middle = (start + end) / 2;
if (tab.endCode[middle] <= code) start = middle;
if (tab.endCode[middle] <= unicode) start = middle;
else end = middle;
}
if (tab.endCode[start] < code) ++start;
if (code < tab.startCode[start]) return 0;
if (tab.endCode[start] < unicode) ++start;
if (unicode < tab.startCode[start]) return 0;
if (tab.idRangeOffset[start] != 0) {
glyfID = tab.glyphIdArray[code - tab.startCode[start] + (tab.idRangeOffset[start] >> 1) - (tab.idRangeOffset.length - start)];
} else glyfID = code + tab.idDelta[start];
glyfID = tab.glyphIdArray[unicode - tab.startCode[start] + (tab.idRangeOffset[start] >> 1) - (tab.idRangeOffset.length - start)];
} else glyfID = unicode + tab.idDelta[start];
glyfID &= 0xFFFF;
} else if (fmt == 6) {
CmapFormat6 tab = (CmapFormat6) table;
int index = code - tab.firstCode;
if (index < 0 || index >= tab.glyphIdArray.length) glyfID = 0;
else glyfID = tab.glyphIdArray[index];
int index = unicode - tab.firstCode;
if (0 <= index && index < tab.glyphIdArray.length) glyfID = tab.glyphIdArray[index];
} else if (fmt == 12) {
CmapFormat12 tab = (CmapFormat12) table;
if (code > tab.groups.get(tab.numGroups - 1).getMiddle()) return 0;
if (unicode > tab.groups.get(tab.numGroups - 1).getMiddle()) return 0;
for (int i = 0; i < tab.numGroups; i++) {
if (tab.groups.get(i).getLeft() <= code && code <= tab.groups.get(i).getMiddle()) {
glyfID = tab.groups.get(i).getRight() + code - tab.groups.get(i).getLeft();
if (tab.groups.get(i).getLeft() <= unicode && unicode <= tab.groups.get(i).getMiddle()) {
glyfID = tab.groups.get(i).getRight() + unicode - tab.groups.get(i).getLeft();
break;
}
}
@ -567,33 +524,54 @@ public class QueryTTF {
}
/**
* 判断Unicode值是否在字体范围内
* 使用Unicode值获取轮廓索引
*
* @param code 传入Unicode十进制
* @return 返回bool查询结果
* @param unicode 传入Unicode
* @return 返回十进制轮廓索引
*/
public boolean inLimit(int code) {
return (limitMix <= code) && (code <= limitMax);
public int getGlyfIndex(int unicode) {
var glyfIndex = unicodeToGlyphIndex.get(unicode);
if (glyfIndex == null) return 0;
return glyfIndex;
}
/**
* 使用Unicode值获取轮廓数据
*
* @param key 传入Unicode十进制
* @param unicode 传入Unicode
* @return 返回轮廓数组的String值
*/
public String getGlyfByCode(int key) {
return codeToGlyph.getOrDefault(key, "");
public String getGlyfByCode(int unicode) {
return unicodeToGlyph.getOrDefault(unicode, "");
}
/**
* 使用轮廓数据获取Unicode值
*
* @param val 传入轮廓数组的String值
* @param glyph 传入轮廓数组的String值
* @return 返回Unicode十进制值
*/
public int getCodeByGlyf(String val) {
public int getCodeByGlyf(String glyph) {
//noinspection ConstantConditions
return glyphToCode.getOrDefault(val, 0);
return glyphToUnicode.getOrDefault(glyph, 0);
}
/**
* 字体轮廓数据转Hex字符串
*
* @param glyph 字体轮廓数据
* @return 返回轮廓数组的String值
*/
public String getHexFromBytes(byte[] glyph) {
if (glyph == null) return "";
StringBuilder sb = new StringBuilder();
for (byte b : glyph) {
String hex = Integer.toHexString(b);
if (hex.length() == 1) {
sb.append("0");//当16进制为个位数时在前面补0
}
sb.append(hex);//将16进制加入字符串
}
return sb.toString().toUpperCase();
}
}

View File

@ -7,6 +7,7 @@ import android.net.wifi.WifiManager
import android.os.Build
import android.os.PowerManager
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import io.legado.app.R
import io.legado.app.base.BaseService
import io.legado.app.constant.AppConst
@ -15,7 +16,16 @@ import io.legado.app.constant.IntentAction
import io.legado.app.constant.NotificationId
import io.legado.app.constant.PreferKey
import io.legado.app.receiver.NetworkChangedListener
import io.legado.app.utils.*
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.postEvent
import io.legado.app.utils.printOnDebug
import io.legado.app.utils.sendToClip
import io.legado.app.utils.servicePendingIntent
import io.legado.app.utils.startService
import io.legado.app.utils.stopService
import io.legado.app.utils.toastOnUi
import io.legado.app.web.HttpServer
import io.legado.app.web.WebSocketServer
import splitties.init.appCtx
@ -33,6 +43,11 @@ class WebService : BaseService() {
context.startService<WebService>()
}
fun startForeground(context: Context) {
val intent = Intent(context, WebService::class.java)
ContextCompat.startForegroundService(context, intent)
}
fun stop(context: Context) {
context.stopService<WebService>()
}

View File

@ -1,10 +1,14 @@
package io.legado.app.service
import android.app.Dialog
import android.app.ForegroundServiceStartNotAllowedException
import android.content.Intent
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import android.view.WindowManager.BadTokenException
import androidx.annotation.RequiresApi
import io.legado.app.R
import io.legado.app.constant.IntentAction
import io.legado.app.utils.printOnDebug
@ -22,6 +26,7 @@ class WebTileService : TileService() {
state = Tile.STATE_ACTIVE
updateTile()
}
IntentAction.stop -> qsTile?.run {
state = Tile.STATE_INACTIVE
updateTile()
@ -49,7 +54,24 @@ class WebTileService : TileService() {
if (WebService.isRun) {
WebService.stop(this)
} else {
WebService.start(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
val dialog = Dialog(this, R.style.AppTheme_Transparent)
dialog.setOnShowListener {
try {
WebService.startForeground(this)
} catch (e: ForegroundServiceStartNotAllowedException) {
e.printStackTrace()
}
dialog.dismiss()
}
try {
showDialog(dialog)
} catch (e: BadTokenException) {
e.printStackTrace()
}
} else {
WebService.start(this)
}
}
}

View File

@ -1,18 +1,15 @@
package io.legado.app.ui.book.read.page
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Picture
import android.graphics.Rect
import android.os.Build
import android.os.SystemClock
import androidx.core.graphics.withClip
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.lib.theme.ThemeStore
import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.utils.screenshot
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeeded
/**
* 自动翻页
@ -24,13 +21,10 @@ class AutoPager(private val readView: ReadView) {
private var scrollOffsetRemain = 0.0
private var scrollOffset = 0
private var lastTimeMillis = 0L
private var bitmap: Bitmap? = null
private var picture: Picture? = null
private var pictureIsDirty = true
private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
private val rect = Rect()
private var canvasRecorder = CanvasRecorderFactory.create()
private val paint by lazy { Paint() }
fun start() {
isRunning = true
paint.color = ThemeStore.accentColor
@ -48,7 +42,7 @@ class AutoPager(private val readView: ReadView) {
readView.curPage.upSelectAble(AppConfig.textSelectAble)
readView.invalidate()
reset()
picture = null
canvasRecorder.recycle()
}
fun pause() {
@ -71,9 +65,12 @@ class AutoPager(private val readView: ReadView) {
progress = 0
scrollOffsetRemain = 0.0
scrollOffset = 0
bitmap?.recycle()
bitmap = null
pictureIsDirty = true
canvasRecorder.invalidate()
}
fun upRecorder() {
canvasRecorder.recycle()
canvasRecorder = CanvasRecorderFactory.create()
}
fun onDraw(canvas: Canvas) {
@ -86,24 +83,12 @@ class AutoPager(private val readView: ReadView) {
} else {
val bottom = progress
val width = readView.width
if (atLeastApi23) {
if (picture == null) {
picture = Picture()
}
if (pictureIsDirty) {
pictureIsDirty = false
readView.nextPage.screenshot(picture!!)
}
canvas.withClip(0, 0, width, bottom) {
drawPicture(picture!!)
}
} else {
if (bitmap == null) {
bitmap = readView.nextPage.screenshot()
}
rect.set(0, 0, width, bottom)
canvas.drawBitmap(bitmap!!, rect, rect, null)
canvasRecorder.recordIfNeeded(readView.nextPage)
canvas.withClip(0, 0, width, bottom) {
canvasRecorder.draw(this)
}
canvas.drawRect(
0f,
bottom.toFloat() - 1,

View File

@ -514,6 +514,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
(pageDelegate as? ScrollPageDelegate)?.noAnim = AppConfig.noAnimScrollPage
if (upRecorder) {
(pageDelegate as? HorizontalPageDelegate)?.upRecorder()
autoPager.upRecorder()
}
pageDelegate?.setViewSize(width, height)
if (isScroll) {

View File

@ -6,7 +6,6 @@ import android.view.View
import androidx.core.view.isGone
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
@ -29,6 +28,7 @@ import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainViewModel
import io.legado.app.utils.cnCompare
import io.legado.app.utils.flowWithLifecycleFirst
import io.legado.app.utils.observeEvent
import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.startActivity
@ -175,7 +175,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
else -> list.sortedByDescending { it.durChapterTime }
}
}.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch {
}.flowWithLifecycleFirst(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch {
AppLog.put("书架更新出错", it)
}.conflate().flowOn(Dispatchers.Default).collect { list ->
binding.tvEmptyMsg.isGone = list.isNotEmpty()

View File

@ -1,13 +1,17 @@
package io.legado.app.utils
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
@ -185,3 +189,18 @@ inline fun <T> Flow<T>.onEachAsyncIndexed(
}.onEach { semaphore.release() }
}.buffer(0)
}
fun <T> Flow<T>.flowWithLifecycleFirst(
lifecycle: Lifecycle,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED
): Flow<T> = callbackFlow {
if (!lifecycle.currentState.isAtLeast(minActiveState)) {
send(first())
}
lifecycle.repeatOnLifecycle(minActiveState) {
this@flowWithLifecycleFirst.collect {
send(it)
}
}
close()
}

View File

@ -1,6 +1,7 @@
package io.legado.app.utils.canvasrecorder
import android.graphics.Canvas
import android.view.View
import androidx.core.graphics.withSave
inline fun CanvasRecorder.recordIfNeeded(
@ -13,6 +14,14 @@ inline fun CanvasRecorder.recordIfNeeded(
return true
}
fun CanvasRecorder.recordIfNeeded(view: View): Boolean {
if (!needRecord()) return false
record(view.width, view.height) {
view.draw(this)
}
return true
}
inline fun CanvasRecorder.record(width: Int, height: Int, block: Canvas.() -> Unit) {
try {
val canvas = beginRecording(width, height)

View File

@ -42,7 +42,7 @@ android.defaults.buildfeatures.shaders=false
# and none from the library's dependencies, thereby reducing the size of the R class for that library.
android.nonTransitiveRClass=true
# https://chromiumdash.appspot.com/releases?platform=Android
CronetVersion=123.0.6312.80
CronetMainVersion=123.0.0.0
CronetVersion=124.0.6367.82
CronetMainVersion=124.0.0.0
android.injected.testOnly=false
android.nonFinalResIds=true