diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.kt b/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.kt
new file mode 100644
index 000000000..ef05182b2
--- /dev/null
+++ b/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.kt
@@ -0,0 +1,483 @@
+package io.legado.app.model.analyzeRule
+
+import io.legado.app.help.JsExtensions
+import kotlin.experimental.and
+
+@ExperimentalUnsignedTypes
+/**
+ * 解析TTF字体
+ *
+ * @see 获取详情
+ */
+class QueryTTF(var font: ByteArray) : JsExtensions {
+ data class Index(var num: Int)
+
+ class FileHeader {
+ var majorVersion: UShort = 0u
+ var minorVersion: UShort = 0u
+ var numOfTables: UShort = 0u
+ var searchRange: UShort = 0u
+ var entrySelector: UShort = 0u
+ var rangeShift: UShort = 0u
+ }
+
+ class TableDirectory {
+ var tag: String = ""
+ var checkSum: UInt = 0u
+ var offset: UInt = 0u
+ var length: UInt = 0u
+ lateinit var data: ByteArray
+ }
+
+ class NameTable {
+ var format: UShort = 0u
+ var count: UShort = 0u
+ var stringOffset: UShort = 0u
+ var Records = ArrayList()
+ }
+
+ class NameRecord {
+ var platformID: UShort = 0u
+ var encodingID: UShort = 0u
+ var languageID: UShort = 0u
+ var nameID: UShort = 0u
+ var length: UShort = 0u
+ var offset: UShort = 0u
+ lateinit var nameBuffer: ByteArray
+ }
+
+ class HeadTable {
+ var majorVersion: UShort = 0u
+ var minorVersion: UShort = 0u
+ var fontRevision: UInt = 0u
+ var checkSumAdjustment: UInt = 0u
+ var magicNumber: UInt = 0u
+ var flags: UShort = 0u
+ var unitsPerEm: UShort = 0u
+ var created: ULong = 0u
+ var modified: ULong = 0u
+ var xMin: Short = 0
+ var yMin: Short = 0
+ var xMax: Short = 0
+ var yMax: Short = 0
+ var macStyle: UShort = 0u
+ var lowestRecPPEM: UShort = 0u
+ var fontDirectionHint: Short = 0
+ var indexToLocFormat: Short = 0
+ var glyphDataFormat: Short = 0
+ }
+
+ class MaxpTable {
+ var majorVersion: UShort = 0u
+ var minorVersion: UShort = 0u
+ var numGlyphs: UShort = 0u // 字体中的字形数量
+ var maxPoints: UShort = 0u
+ var maxContours: UShort = 0u
+ var maxCompositePoints: UShort = 0u
+ var maxCompositeContours: UShort = 0u
+ var maxZones: UShort = 0u
+ var maxTwilightPoints: UShort = 0u
+ var maxStorage: UShort = 0u
+ var maxFunctionDefs: UShort = 0u
+ var maxInstructionDefs: UShort = 0u
+ var maxStackElements: UShort = 0u
+ var maxSizeOfInstructions: UShort = 0u
+ var maxComponentElements: UShort = 0u
+ var maxComponentDepth: UShort = 0u
+ }
+
+ class CmapTable {
+ var version: UShort = 0u
+ var numTables: UShort = 0u
+ var records = ArrayList()
+ var tables = mutableMapOf()
+ }
+
+ class EncodingRecord {
+ var platformID: UShort = 0u
+ var encodingID: UShort = 0u
+ var offset: UInt = 0u
+ }
+
+ class Format0 {
+ var format: UShort = 0u
+ var length: UShort = 0u
+ var language: UShort = 0u
+ var glyphIdArray = ByteArray(256)
+ }
+
+ class Format4 {
+ var format: UShort = 0u
+ var length: UShort = 0u
+ var language: UShort = 0u
+ var segCountX2: UShort = 0u
+ var searchRange: UShort = 0u
+ var entrySelector: UShort = 0u
+ var rangeShift: UShort = 0u
+ lateinit var endCode: IntArray // UInt16[]
+ var reservedPad: UShort = 0u
+ lateinit var startCode: IntArray // UInt16[]
+ lateinit var idDelta: ShortArray
+ lateinit var idRangeOffset: IntArray // UInt16[]
+ var glyphIdArray = ArrayList()
+ }
+
+ class GlyfTable {
+ var numberOfContours: Short = 0
+ var xMin: Short = 0
+ var yMin: Short = 0
+ var xMax: Short = 0
+ var yMax: Short = 0
+ lateinit var endPtsOfContours: IntArray // UInt16[]
+ var instructionLength: Int = 0 // UInt16
+ lateinit var instructions: ByteArray
+ lateinit var flags: ByteArray
+ lateinit var xCoordinates: Array
+ lateinit var yCoordinates: Array
+ }
+
+ var Header = FileHeader()
+
+ var Tables = mutableMapOf()
+
+ var name = NameTable()
+
+ var head = HeadTable()
+
+ var maxp = MaxpTable()
+
+ var loca = ArrayList()
+
+ var cmap = CmapTable()
+
+ var glyf = ArrayList()
+
+ // ByteArray转Long整数
+ private fun byteArrToUintx(buff: ByteArray): Long {
+ var result: Long = 0
+ var n: Int = 0
+ var i: Int = buff.size
+ while (i > 0) {
+ result = result or buff[--i].toLong().shl(n)
+ n += 8
+ }
+ return result
+ }
+
+ // 索引变量
+ var index = 0
+
+ // 从索引index开始拷贝指定长度的数组
+ private fun ByteArray.copyOfIndex(length: Int): ByteArray {
+ val fromIndex = index
+ index += length
+ return this.copyOfRange(fromIndex, fromIndex + length)
+ }
+
+ // 从索引fromIndex开始拷贝指定长度的数组
+ private fun ByteArray.copyOfIndex(fromIndex: Int, length: Int): ByteArray {
+ return this.copyOfRange(fromIndex, fromIndex + length)
+ }
+
+ init {
+
+ // 解析文件头
+ Header = FileHeader()
+ // 跳过不需要的数据条目
+ index = 4
+ Header.numOfTables = byteArrToUintx(font.copyOfIndex(2)).toUShort()
+
+ // 获取数据表
+ index = 12
+ for (i in 0 until Header.numOfTables.toInt()) {
+ val table = TableDirectory()
+ table.tag = font.copyOfIndex(4).toString(Charsets.US_ASCII)
+ table.checkSum = byteArrToUintx(font.copyOfIndex(4)).toUInt()
+ table.offset = byteArrToUintx(font.copyOfIndex(4)).toUInt()
+ table.length = byteArrToUintx(font.copyOfIndex(4)).toUInt()
+ table.data = font.copyOfIndex(table.offset.toInt(), table.length.toInt())
+ Tables[table.tag] = table
+ }
+
+ // 解析表 name (字体信息,包含版权、名称、作者等...)
+ run {
+ val data = Tables["name"]!!.data
+ index = 0
+ name.format = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ name.count = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ name.stringOffset = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+
+ for (i in 0 until name.count.toInt()) {
+ val record = NameRecord()
+ record.platformID = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ record.encodingID = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ record.languageID = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ record.nameID = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ record.length = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ record.offset = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ record.nameBuffer = data.copyOfIndex((name.stringOffset + record.offset).toInt(), record.length.toInt())
+ name.Records.add(record)
+ }
+ }
+
+ // 解析表 head (获取 head.indexToLocFormat)
+ run {
+ val data = Tables["head"]!!.data
+ index = 0
+ head.majorVersion = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ head.minorVersion = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ head.fontRevision = byteArrToUintx(data.copyOfIndex(4)).toUInt()
+ head.checkSumAdjustment = byteArrToUintx(data.copyOfIndex(4)).toUInt()
+ head.magicNumber = byteArrToUintx(data.copyOfIndex(4)).toUInt()
+ head.flags = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ head.unitsPerEm = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ head.created = byteArrToUintx(data.copyOfIndex(8)).toULong()
+ head.modified = byteArrToUintx(data.copyOfIndex(8)).toULong()
+ head.xMin = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ head.yMin = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ head.xMax = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ head.yMax = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ head.macStyle = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ head.lowestRecPPEM = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ head.fontDirectionHint = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ head.indexToLocFormat = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ head.glyphDataFormat = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ }
+
+ // 解析表 maxp (获取 maxp.numGlyphs)
+ run {
+ val data = Tables["maxp"]!!.data
+ index = 0
+ index = 0
+ maxp.majorVersion = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.minorVersion = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.numGlyphs = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxPoints = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxContours = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxCompositePoints = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxCompositeContours = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxZones = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxTwilightPoints = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxStorage = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxFunctionDefs = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxInstructionDefs = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxStackElements = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxSizeOfInstructions = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxComponentElements = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ maxp.maxComponentDepth = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ }
+
+ // 解析表 loca (轮廓数据偏移地址表)
+ run {
+ val data = Tables["maxp"]!!.data
+ val offset = if (head.indexToLocFormat.equals(0)) 2 else 4
+ index = 0
+ while (index < data.size) {
+ loca.add((byteArrToUintx(data.copyOfIndex(offset)) * (if (offset == 4) 1 else 2)).toUInt())
+ index += offset
+ }
+ }
+
+ // 解析表 cmap (Unicode编码轮廓索引对照表)
+ run {
+ val data = Tables["cmap"]!!.data
+ index = 0
+ cmap.version = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ cmap.numTables = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+
+ for (i in 0 until cmap.numTables.toInt()) {
+ val record = EncodingRecord()
+ record.platformID = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ record.encodingID = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ record.offset = byteArrToUintx(data.copyOfIndex(4)).toUInt()
+ cmap.records.add(record)
+ val tmpIndex = index; // 缓存索引
+
+ index = record.offset.toInt()
+ val fmt = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ val len = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ val lang = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ if (fmt.equals(0)) {
+ val ft = Format0()
+ ft.format = fmt
+ ft.length = len
+ ft.language = lang
+ ft.glyphIdArray = data.copyOfIndex(index, len.toInt() - 6)
+ cmap.tables[fmt.toInt()] = ft
+ } else if (fmt.equals(4)) {
+ val ft = Format4()
+ ft.format = fmt
+ ft.length = len
+ ft.language = lang
+ ft.segCountX2 = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ val segCount = ft.segCountX2.toInt() / 2
+ ft.searchRange = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ ft.entrySelector = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ ft.rangeShift = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ ft.endCode = IntArray(segCount)
+ for (n in 0 until segCount) {
+ ft.endCode[n] = byteArrToUintx(data.copyOfIndex(2)).toInt()
+ }
+ ft.reservedPad = byteArrToUintx(data.copyOfIndex(2)).toUShort()
+ ft.startCode = IntArray(segCount)
+ for (n in 0 until segCount) {
+ ft.startCode[n] = byteArrToUintx(data.copyOfIndex(2)).toInt()
+ }
+ ft.idDelta = ShortArray(segCount)
+ for (n in 0 until segCount) {
+ ft.idDelta[n] = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ }
+ ft.idRangeOffset = IntArray(segCount)
+ for (n in 0 until segCount) {
+ ft.idRangeOffset[n] = byteArrToUintx(data.copyOfIndex(2)).toInt()
+ }
+ while (index < len.toInt()) {
+ ft.glyphIdArray.add(byteArrToUintx(data.copyOfIndex(2)).toUShort())
+ }
+ cmap.tables[fmt.toInt()] = ft
+ }
+ index = tmpIndex; // 读取缓存的索引
+ }
+ }
+
+ // 解析表 glyf (字体轮廓数据表)
+ run {
+ val data = Tables["glyf"]!!.data
+ for (i in 0 until maxp.numGlyphs.toInt()) {
+ index = loca[i].toInt()
+ val numberOfContours = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ val xMin = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ val yMin = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ val xMax = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ val yMax = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ if (numberOfContours > 0) {
+ val g = GlyfTable()
+ g.numberOfContours = numberOfContours
+ g.xMin = xMin
+ g.yMin = yMin
+ g.xMax = xMax
+ g.yMax = yMax
+ g.endPtsOfContours = IntArray(numberOfContours.toInt())
+ for (n in 0 until numberOfContours) {
+ g.endPtsOfContours[n] = byteArrToUintx(data.copyOfIndex(2)).toInt(); // 这里数据源为UShort
+ }
+ g.instructionLength = byteArrToUintx(data.copyOfIndex(2)).toInt(); // 这里数据源为UShort
+ g.instructions = data.copyOfIndex(g.instructionLength)
+ val flagLength = g.endPtsOfContours.last() + 1
+ g.flags = ByteArray(flagLength)
+ var n = 0
+ while (n < flagLength) {
+ val flag = data.copyOfIndex(1).first()
+ g.flags[n] = flag
+ if (!(flag and 0x08).equals(0)) {
+ var j = data.copyOfIndex(1).first()
+ while (j > 0) {
+ --j
+ g.flags[++n] = flag
+ }
+ }
+ ++n
+ }
+ // 获取x轴相对坐标
+ g.xCoordinates = Array(flagLength) { 0 }
+ for (n in 0 until flagLength) {
+ val xByte = !(g.flags[n] and 0x02).equals(0)
+ val xSame = !(g.flags[n] and 0x10).equals(0)
+ if (xByte) {
+ g.xCoordinates[n] = ((if (xSame) 1 else -1) * data.copyOfIndex(1).first()).toShort()
+ } else {
+ if (xSame) g.xCoordinates[n] = 0
+ else {
+ g.xCoordinates[n] = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ }
+ }
+ }
+ // 获取y轴相对坐标
+ g.yCoordinates = Array(flagLength) { 0 }
+ for (n in 0 until flagLength) {
+ val yByte = !(g.flags[n] and 0x04).equals(0)
+ val ySame = !(g.flags[n] and 0x20).equals(0)
+ if (yByte) {
+ g.yCoordinates[n] = ((if (ySame) 1 else -1) * data.copyOfIndex(1).first()).toShort()
+ } else {
+ if (ySame) g.yCoordinates[n] = 0
+ else {
+ g.yCoordinates[n] = byteArrToUintx(data.copyOfIndex(2)).toShort()
+ }
+ }
+ }
+ /*
+ PS:因为不需要绘制字体,转换就省了
+ // 相对坐标转绝对坐标
+ for (n in 0 until flagLength) {
+ g.xCoordinates[n] = (g.xCoordinates[n] + g.xCoordinates[n - 1]).toShort()
+ g.yCoordinates[n] = (g.yCoordinates[n] + g.yCoordinates[n - 1]).toShort()
+ }
+ */
+
+ glyf.add(g)
+ } else {
+ // 复合字体暂不处理,以后用到再加
+ }
+ }
+ }
+ }
+
+ /**
+ * 获取字体信息(默认获取字体名称)
+ * @see 获取详情
+ */
+ fun GetName(nameID: Int = 1): String {
+ for (record in name.Records) {
+ if (!record.nameID.equals(nameID)) continue
+ if (record.platformID.equals(1) && record.encodingID.equals(0)) {
+ return record.nameBuffer.toString(Charsets.UTF_8)
+ } else if (record.platformID.equals(3) && record.encodingID.equals(1)) {
+ return record.nameBuffer.toString(Charsets.UTF_16BE)
+ }
+ }
+ return "error"
+ }
+
+ /**
+ * 获取字体轮廓 (fontCode:十进制Unicode字符编码)
+ */
+ fun GetGlyf(fontCode: Int): ArrayList {
+ var fontIndex = -1; // 轮廓索引
+ val fmt4 = cmap.tables.getOrDefault(4, null)
+ if (fmt4 != null) {
+ val fmt = (fmt4 as Format4)
+ val endCode = fmt.endCode
+ for (i in endCode.indices) {
+ if (endCode[i] == fontCode) {
+ fontIndex = i
+ break
+ }
+ }
+ if (fontIndex == -1) fontIndex = 0
+ else if (fontCode < fmt.startCode[fontIndex]) fontIndex = 0
+ else {
+ if (fmt.idRangeOffset[fontIndex] != 0) {
+ fontIndex =
+ fmt.glyphIdArray[fontCode - fmt.startCode[fontIndex] + fmt.idRangeOffset[fontIndex].ushr(1) - (fmt.idRangeOffset.size - fontIndex)].toInt()
+ } else fontIndex = fontCode + fmt.idDelta[fontIndex]
+ fontIndex = fontIndex and 0xFFFF
+ }
+ } else {
+ val fmt0 = cmap.tables.getOrDefault(0, null)
+ if (fmt0 != null) {
+ val fmt = (fmt0 as Format0)
+ val glyphs = fmt.glyphIdArray
+ fontIndex = if (fontCode > glyphs.size) 0 else glyphs[fontCode].toInt()
+ }
+ }
+
+ val glyph = ArrayList()
+ if (fontIndex > -1) {
+ // 将字体的轮廓坐标合并到一起返回,方便使用
+ glyph.addAll(glyf[fontIndex].xCoordinates)
+ glyph.addAll(glyf[fontIndex].yCoordinates)
+ }
+ return glyph
+ }
+}
\ No newline at end of file