From 9ea11702c3349fe2befd199327fa8a7e926a24aa Mon Sep 17 00:00:00 2001 From: Antecer Date: Sun, 15 Nov 2020 22:48:06 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0TTF=E5=AD=97=E4=BD=93?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/model/analyzeRule/QueryTTF.kt | 483 ++++++++++++++++++ 1 file changed, 483 insertions(+) create mode 100644 app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.kt 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 From 93b80d65f5d32eac5b7b378ac5f2582d9c927ade Mon Sep 17 00:00:00 2001 From: Antecer Date: Sun, 15 Nov 2020 22:50:12 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index ef05182b2..8a1e91493 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.kt @@ -6,8 +6,8 @@ import kotlin.experimental.and @ExperimentalUnsignedTypes /** * 解析TTF字体 - * * @see 获取详情 + * @see 基于Javascript的TTF解析器 */ class QueryTTF(var font: ByteArray) : JsExtensions { data class Index(var num: Int)