|
|
|
@ -1,6 +1,7 @@ |
|
|
|
|
package io.legado.app.model.analyzeRule |
|
|
|
|
|
|
|
|
|
import io.legado.app.help.JsExtensions |
|
|
|
|
import java.nio.charset.Charset |
|
|
|
|
import kotlin.experimental.and |
|
|
|
|
|
|
|
|
|
@ExperimentalUnsignedTypes |
|
|
|
@ -9,10 +10,8 @@ import kotlin.experimental.and |
|
|
|
|
* @see <a href="https://docs.microsoft.com/en-us/typography/opentype/spec/">获取详情</a> |
|
|
|
|
* @see <a href="https://photopea.github.io/Typr.js/demo/index.html">基于Javascript的TTF解析器</a> |
|
|
|
|
*/ |
|
|
|
|
class QueryTTF(var font: ByteArray) : JsExtensions { |
|
|
|
|
data class Index(var num: Int) |
|
|
|
|
|
|
|
|
|
class FileHeader { |
|
|
|
|
class QueryTTF(var Font: Array<Byte>) : JsExtensions { |
|
|
|
|
private class Header { |
|
|
|
|
var majorVersion: UShort = 0u |
|
|
|
|
var minorVersion: UShort = 0u |
|
|
|
|
var numOfTables: UShort = 0u |
|
|
|
@ -21,32 +20,30 @@ class QueryTTF(var font: ByteArray) : JsExtensions { |
|
|
|
|
var rangeShift: UShort = 0u |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class TableDirectory { |
|
|
|
|
private class Directory { |
|
|
|
|
var tag: String = "" |
|
|
|
|
var checkSum: UInt = 0u |
|
|
|
|
var offset: UInt = 0u |
|
|
|
|
var length: UInt = 0u |
|
|
|
|
lateinit var data: ByteArray |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class NameTable { |
|
|
|
|
private class NameLayout { |
|
|
|
|
var format: UShort = 0u |
|
|
|
|
var count: UShort = 0u |
|
|
|
|
var stringOffset: UShort = 0u |
|
|
|
|
var Records = ArrayList<NameRecord>() |
|
|
|
|
var records = ArrayList<NameRecord>() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class NameRecord { |
|
|
|
|
private 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 { |
|
|
|
|
private class HeadLayout { |
|
|
|
|
var majorVersion: UShort = 0u |
|
|
|
|
var minorVersion: UShort = 0u |
|
|
|
|
var fontRevision: UInt = 0u |
|
|
|
@ -67,7 +64,7 @@ class QueryTTF(var font: ByteArray) : JsExtensions { |
|
|
|
|
var glyphDataFormat: Short = 0 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class MaxpTable { |
|
|
|
|
private class MaxpLayout { |
|
|
|
|
var majorVersion: UShort = 0u |
|
|
|
|
var minorVersion: UShort = 0u |
|
|
|
|
var numGlyphs: UShort = 0u // 字体中的字形数量 |
|
|
|
@ -86,27 +83,27 @@ class QueryTTF(var font: ByteArray) : JsExtensions { |
|
|
|
|
var maxComponentDepth: UShort = 0u |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class CmapTable { |
|
|
|
|
private class CmapLayout { |
|
|
|
|
var version: UShort = 0u |
|
|
|
|
var numTables: UShort = 0u |
|
|
|
|
var records = ArrayList<EncodingRecord>() |
|
|
|
|
var tables = mutableMapOf<Int, Any>() |
|
|
|
|
var records = ArrayList<CmapRecord>() |
|
|
|
|
var tables = mutableMapOf<UInt, Any>() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class EncodingRecord { |
|
|
|
|
private class CmapRecord { |
|
|
|
|
var platformID: UShort = 0u |
|
|
|
|
var encodingID: UShort = 0u |
|
|
|
|
var offset: UInt = 0u |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class Format0 { |
|
|
|
|
private class CmapFormat0 { |
|
|
|
|
var format: UShort = 0u |
|
|
|
|
var length: UShort = 0u |
|
|
|
|
var language: UShort = 0u |
|
|
|
|
var glyphIdArray = ByteArray(256) |
|
|
|
|
var glyphIdArray = ArrayList<Byte>() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class Format4 { |
|
|
|
|
private class CmapFormat4 { |
|
|
|
|
var format: UShort = 0u |
|
|
|
|
var length: UShort = 0u |
|
|
|
|
var language: UShort = 0u |
|
|
|
@ -114,377 +111,493 @@ class QueryTTF(var font: ByteArray) : JsExtensions { |
|
|
|
|
var searchRange: UShort = 0u |
|
|
|
|
var entrySelector: UShort = 0u |
|
|
|
|
var rangeShift: UShort = 0u |
|
|
|
|
lateinit var endCode: IntArray // UInt16[] |
|
|
|
|
lateinit var endCode: ArrayList<UShort> // UInt16[] |
|
|
|
|
var reservedPad: UShort = 0u |
|
|
|
|
lateinit var startCode: IntArray // UInt16[] |
|
|
|
|
lateinit var idDelta: ShortArray |
|
|
|
|
lateinit var idRangeOffset: IntArray // UInt16[] |
|
|
|
|
lateinit var startCode: ArrayList<UShort> // UInt16[] |
|
|
|
|
lateinit var idDelta: ArrayList<Short> |
|
|
|
|
lateinit var idRangeOffset: ArrayList<UShort> // UInt16[] |
|
|
|
|
var glyphIdArray = ArrayList<UShort>() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private class CmapFormat6 { |
|
|
|
|
var format: UShort = 0u |
|
|
|
|
var length: UShort = 0u |
|
|
|
|
var language: UShort = 0u |
|
|
|
|
var firstCode: UShort = 0u |
|
|
|
|
var entryCount: UShort = 0u |
|
|
|
|
var glyphIdArray = ArrayList<UShort>() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class GlyfTable { |
|
|
|
|
private class CmapFormat12 { |
|
|
|
|
var format: UShort = 0u |
|
|
|
|
var reserved: UShort = 0u |
|
|
|
|
var length: UInt = 0u |
|
|
|
|
var language: UInt = 0u |
|
|
|
|
var numGroups: UInt = 0u |
|
|
|
|
lateinit var groups: ArrayList<Triple<UInt, UInt, UInt>> |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private class GlyfLayout { |
|
|
|
|
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<Short> |
|
|
|
|
lateinit var yCoordinates: Array<Short> |
|
|
|
|
lateinit var endPtsOfContours: ArrayList<UShort> // UInt16[] |
|
|
|
|
var instructionLength: UShort = 0u // UInt16 |
|
|
|
|
lateinit var instructions: ArrayList<Byte> |
|
|
|
|
lateinit var flags: ArrayList<Byte> |
|
|
|
|
lateinit var xCoordinates: ArrayList<Short> |
|
|
|
|
lateinit var yCoordinates: ArrayList<Short> |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var Header = FileHeader() |
|
|
|
|
private class ByteArrayReader(var Buffer: Array<Byte>, var Index: Int) { |
|
|
|
|
fun ReadUIntX(len: Long): ULong { |
|
|
|
|
var result: ULong = 0u; |
|
|
|
|
for (i in 0 until len) { |
|
|
|
|
result.shl(8); |
|
|
|
|
result = result or Buffer[Index++].toULong() |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var Tables = mutableMapOf<String, TableDirectory>() |
|
|
|
|
fun ReadUInt64(): ULong { |
|
|
|
|
return ReadUIntX(8) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var name = NameTable() |
|
|
|
|
fun ReadInt64(): Long { |
|
|
|
|
return ReadUIntX(8).toLong() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var head = HeadTable() |
|
|
|
|
fun ReadUInt32(): UInt { |
|
|
|
|
return ReadUIntX(4).toUInt() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var maxp = MaxpTable() |
|
|
|
|
fun ReadInt32(): Int { |
|
|
|
|
return ReadUIntX(4).toInt() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var loca = ArrayList<UInt>() |
|
|
|
|
fun ReadUInt16(): UShort { |
|
|
|
|
return ReadUIntX(2).toUShort() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var cmap = CmapTable() |
|
|
|
|
fun ReadInt16(): Short { |
|
|
|
|
return ReadUIntX(2).toShort() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var glyf = ArrayList<GlyfTable>() |
|
|
|
|
fun ReadStrings(len: Int, charset: Charset): String { |
|
|
|
|
if (len <= 0) return "" |
|
|
|
|
val result = ByteArray(len) |
|
|
|
|
for (i in 0 until len) { |
|
|
|
|
result[i] = Buffer[Index++] |
|
|
|
|
} |
|
|
|
|
return result.toString(charset) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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 |
|
|
|
|
fun GetByte(): Byte { |
|
|
|
|
return Buffer[Index++] |
|
|
|
|
} |
|
|
|
|
return result |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 索引变量 |
|
|
|
|
var index = 0 |
|
|
|
|
fun GetBytes(len: Int): ArrayList<Byte> { |
|
|
|
|
if (len <= 0) return ArrayList(0) |
|
|
|
|
val result = ArrayList<Byte>(len) |
|
|
|
|
for (i in 0 until len) { |
|
|
|
|
result[i] = Buffer[Index++] |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 从索引index开始拷贝指定长度的数组 |
|
|
|
|
private fun ByteArray.copyOfIndex(length: Int): ByteArray { |
|
|
|
|
val fromIndex = index |
|
|
|
|
index += length |
|
|
|
|
return this.copyOfRange(fromIndex, fromIndex + length) |
|
|
|
|
} |
|
|
|
|
fun GetUInt16Array(len: Int): ArrayList<UShort> { |
|
|
|
|
if (len <= 0) return ArrayList(0) |
|
|
|
|
val result = ArrayList<UShort>(len) |
|
|
|
|
for (i in 0 until len) { |
|
|
|
|
result[i] = ReadUInt16() |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 从索引fromIndex开始拷贝指定长度的数组 |
|
|
|
|
private fun ByteArray.copyOfIndex(fromIndex: Int, length: Int): ByteArray { |
|
|
|
|
return this.copyOfRange(fromIndex, fromIndex + length) |
|
|
|
|
fun GetInt16Array(len: Int): ArrayList<Short> { |
|
|
|
|
if (len <= 0) return ArrayList(0) |
|
|
|
|
val result = ArrayList<Short>(len) |
|
|
|
|
for (i in 0 until len) { |
|
|
|
|
result[i] = ReadInt16() |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
init { |
|
|
|
|
private var FontReader: ByteArrayReader |
|
|
|
|
private var fileHeader = Header() |
|
|
|
|
private var directorys = ArrayList<Directory>() |
|
|
|
|
private var name = NameLayout() |
|
|
|
|
private var head = HeadLayout() |
|
|
|
|
private var maxp = MaxpLayout() |
|
|
|
|
private var loca = ArrayList<UInt>() |
|
|
|
|
private var cmap = CmapLayout() |
|
|
|
|
private var glyf = ArrayList<GlyfLayout>() |
|
|
|
|
private var unicodeMap = mutableMapOf<Int, ArrayList<Short>>() |
|
|
|
|
|
|
|
|
|
// 解析文件头 |
|
|
|
|
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 |
|
|
|
|
init { |
|
|
|
|
FontReader = ByteArrayReader(Font, 0) |
|
|
|
|
// 获取文件头 |
|
|
|
|
fileHeader.majorVersion = FontReader.ReadUInt16() |
|
|
|
|
fileHeader.minorVersion = FontReader.ReadUInt16(); |
|
|
|
|
fileHeader.numOfTables = FontReader.ReadUInt16(); |
|
|
|
|
fileHeader.searchRange = FontReader.ReadUInt16(); |
|
|
|
|
fileHeader.entrySelector = FontReader.ReadUInt16(); |
|
|
|
|
fileHeader.rangeShift = FontReader.ReadUInt16(); |
|
|
|
|
// 获取目录 |
|
|
|
|
for (i in 0 until fileHeader.numOfTables.toInt()) { |
|
|
|
|
val tag = FontReader.ReadStrings(4, Charsets.US_ASCII) |
|
|
|
|
val t = Directory(); |
|
|
|
|
t.tag = tag |
|
|
|
|
t.checkSum = FontReader.ReadUInt32() |
|
|
|
|
t.offset = FontReader.ReadUInt32() |
|
|
|
|
t.length = FontReader.ReadUInt32() |
|
|
|
|
directorys.add(t) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 解析表 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) |
|
|
|
|
for (Temp in directorys) { |
|
|
|
|
if (Temp.tag == "name") { |
|
|
|
|
FontReader.Index = Temp.offset.toInt(); |
|
|
|
|
name.format = FontReader.ReadUInt16(); |
|
|
|
|
name.count = FontReader.ReadUInt16(); |
|
|
|
|
name.stringOffset = FontReader.ReadUInt16(); |
|
|
|
|
|
|
|
|
|
for (i in 0 until name.count.toInt()) { |
|
|
|
|
val record = NameRecord() |
|
|
|
|
record.platformID = FontReader.ReadUInt16() |
|
|
|
|
record.encodingID = FontReader.ReadUInt16() |
|
|
|
|
record.languageID = FontReader.ReadUInt16() |
|
|
|
|
record.nameID = FontReader.ReadUInt16() |
|
|
|
|
record.length = FontReader.ReadUInt16() |
|
|
|
|
record.offset = FontReader.ReadUInt16() |
|
|
|
|
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() |
|
|
|
|
for (Temp in directorys) { |
|
|
|
|
if (Temp.tag == "head") { |
|
|
|
|
FontReader.Index = Temp.offset.toInt(); |
|
|
|
|
head.majorVersion = FontReader.ReadUInt16(); |
|
|
|
|
head.minorVersion = FontReader.ReadUInt16(); |
|
|
|
|
head.fontRevision = FontReader.ReadUInt32(); |
|
|
|
|
head.checkSumAdjustment = FontReader.ReadUInt32(); |
|
|
|
|
head.magicNumber = FontReader.ReadUInt32(); |
|
|
|
|
head.flags = FontReader.ReadUInt16(); |
|
|
|
|
head.unitsPerEm = FontReader.ReadUInt16(); |
|
|
|
|
head.created = FontReader.ReadUInt64(); |
|
|
|
|
head.modified = FontReader.ReadUInt64(); |
|
|
|
|
head.xMin = FontReader.ReadInt16(); |
|
|
|
|
head.yMin = FontReader.ReadInt16(); |
|
|
|
|
head.xMax = FontReader.ReadInt16(); |
|
|
|
|
head.yMax = FontReader.ReadInt16(); |
|
|
|
|
head.macStyle = FontReader.ReadUInt16(); |
|
|
|
|
head.lowestRecPPEM = FontReader.ReadUInt16(); |
|
|
|
|
head.fontDirectionHint = FontReader.ReadInt16(); |
|
|
|
|
head.indexToLocFormat = FontReader.ReadInt16(); |
|
|
|
|
head.glyphDataFormat = FontReader.ReadInt16(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 解析表 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() |
|
|
|
|
for (Temp in directorys) { |
|
|
|
|
if (Temp.tag == "maxp") { |
|
|
|
|
FontReader.Index = Temp.offset.toInt(); |
|
|
|
|
maxp.majorVersion = FontReader.ReadUInt16(); |
|
|
|
|
maxp.minorVersion = FontReader.ReadUInt16(); |
|
|
|
|
maxp.numGlyphs = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxPoints = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxContours = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxCompositePoints = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxCompositeContours = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxZones = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxTwilightPoints = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxStorage = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxFunctionDefs = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxInstructionDefs = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxStackElements = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxSizeOfInstructions = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxComponentElements = FontReader.ReadUInt16(); |
|
|
|
|
maxp.maxComponentDepth = FontReader.ReadUInt16(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 解析表 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 |
|
|
|
|
for (Temp in directorys) { |
|
|
|
|
if (Temp.tag == "loca") { |
|
|
|
|
FontReader.Index = Temp.offset.toInt() |
|
|
|
|
val offset: UInt = if (head.indexToLocFormat.toInt() == 0) 2u else 4u |
|
|
|
|
var i: UInt = 0u |
|
|
|
|
while (i < Temp.length) { |
|
|
|
|
loca.add(if (offset == 2u) FontReader.ReadUInt16().toUInt().shl(1) else FontReader.ReadUInt32()) |
|
|
|
|
i += 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()) |
|
|
|
|
for (Temp in directorys) { |
|
|
|
|
if (Temp.tag == "cmap") { |
|
|
|
|
FontReader.Index = Temp.offset.toInt() |
|
|
|
|
cmap.version = FontReader.ReadUInt16() |
|
|
|
|
cmap.numTables = FontReader.ReadUInt16() |
|
|
|
|
|
|
|
|
|
for (i in 0 until cmap.numTables.toInt()) { |
|
|
|
|
val record = CmapRecord() |
|
|
|
|
record.platformID = FontReader.ReadUInt16() |
|
|
|
|
record.encodingID = FontReader.ReadUInt16() |
|
|
|
|
record.offset = FontReader.ReadUInt32() |
|
|
|
|
cmap.records.add(record) |
|
|
|
|
} |
|
|
|
|
for (i in 0 until cmap.numTables.toInt()) { |
|
|
|
|
val fmtOffset = cmap.records[i].offset; |
|
|
|
|
FontReader.Index = (Temp.offset + fmtOffset).toInt(); |
|
|
|
|
val EndIndex = FontReader.Index; |
|
|
|
|
|
|
|
|
|
val format = FontReader.ReadUInt16(); |
|
|
|
|
|
|
|
|
|
if (cmap.tables.contains(fmtOffset.toInt())) continue; |
|
|
|
|
when { |
|
|
|
|
format.equals(0) -> { |
|
|
|
|
val fmt = CmapFormat0() |
|
|
|
|
fmt.format = format |
|
|
|
|
fmt.length = FontReader.ReadUInt16() |
|
|
|
|
fmt.language = FontReader.ReadUInt16() |
|
|
|
|
fmt.glyphIdArray = FontReader.GetBytes(fmt.length.toInt() - 6) |
|
|
|
|
cmap.tables[fmtOffset] = fmt |
|
|
|
|
} |
|
|
|
|
format.equals(4) -> { |
|
|
|
|
val fmt = CmapFormat4() |
|
|
|
|
fmt.format = format |
|
|
|
|
fmt.length = FontReader.ReadUInt16() |
|
|
|
|
fmt.language = FontReader.ReadUInt16() |
|
|
|
|
fmt.segCountX2 = FontReader.ReadUInt16() |
|
|
|
|
val segCount = fmt.segCountX2.toInt() / 2 |
|
|
|
|
fmt.searchRange = FontReader.ReadUInt16() |
|
|
|
|
fmt.entrySelector = FontReader.ReadUInt16() |
|
|
|
|
fmt.rangeShift = FontReader.ReadUInt16() |
|
|
|
|
fmt.endCode = FontReader.GetUInt16Array(segCount) |
|
|
|
|
fmt.reservedPad = FontReader.ReadUInt16() |
|
|
|
|
fmt.startCode = FontReader.GetUInt16Array(segCount) |
|
|
|
|
fmt.idDelta = FontReader.GetInt16Array(segCount) |
|
|
|
|
fmt.idRangeOffset = FontReader.GetUInt16Array(segCount) |
|
|
|
|
fmt.glyphIdArray = FontReader.GetUInt16Array((fmt.length.toInt() - (FontReader.Index - EndIndex)) / 2) |
|
|
|
|
cmap.tables[fmtOffset] = fmt |
|
|
|
|
} |
|
|
|
|
format.equals(6) -> { |
|
|
|
|
val fmt = CmapFormat6() |
|
|
|
|
fmt.format = format |
|
|
|
|
fmt.length = FontReader.ReadUInt16() |
|
|
|
|
fmt.language = FontReader.ReadUInt16() |
|
|
|
|
fmt.firstCode = FontReader.ReadUInt16() |
|
|
|
|
fmt.entryCount = FontReader.ReadUInt16() |
|
|
|
|
fmt.glyphIdArray = FontReader.GetUInt16Array(fmt.entryCount.toInt()) |
|
|
|
|
cmap.tables[fmtOffset] = fmt |
|
|
|
|
} |
|
|
|
|
format.equals(12) -> { |
|
|
|
|
val fmt = CmapFormat12() |
|
|
|
|
fmt.format = format |
|
|
|
|
fmt.reserved = FontReader.ReadUInt16(); |
|
|
|
|
fmt.length = FontReader.ReadUInt32(); |
|
|
|
|
fmt.language = FontReader.ReadUInt32(); |
|
|
|
|
fmt.numGroups = FontReader.ReadUInt32(); |
|
|
|
|
for (n in 0 until fmt.numGroups.toLong()) { |
|
|
|
|
fmt.groups.add(Triple(FontReader.ReadUInt32(), FontReader.ReadUInt32(), FontReader.ReadUInt32())) |
|
|
|
|
} |
|
|
|
|
cmap.tables[fmtOffset] = fmt |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
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 |
|
|
|
|
for (Temp in directorys) { |
|
|
|
|
if (Temp.tag == "glyf") { |
|
|
|
|
FontReader.Index = Temp.offset.toInt() |
|
|
|
|
for (i in 0 until maxp.numGlyphs.toInt()) { |
|
|
|
|
FontReader.Index = (Temp.offset + loca[i]).toInt() |
|
|
|
|
|
|
|
|
|
val g = GlyfLayout() |
|
|
|
|
g.numberOfContours = FontReader.ReadInt16() |
|
|
|
|
g.xMin = FontReader.ReadInt16() |
|
|
|
|
g.yMin = FontReader.ReadInt16() |
|
|
|
|
g.xMax = FontReader.ReadInt16() |
|
|
|
|
g.yMax = FontReader.ReadInt16() |
|
|
|
|
if (g.numberOfContours > 0) { |
|
|
|
|
g.endPtsOfContours = FontReader.GetUInt16Array(g.numberOfContours.toInt()) |
|
|
|
|
g.instructionLength = FontReader.ReadUInt16() |
|
|
|
|
g.instructions = FontReader.GetBytes(g.instructionLength.toInt()) |
|
|
|
|
val flagLength = g.endPtsOfContours.last().toInt() + 1 |
|
|
|
|
// 获取轮廓点描述标志 |
|
|
|
|
g.flags = ArrayList(flagLength) |
|
|
|
|
var n = 0 |
|
|
|
|
while (n < flagLength) { |
|
|
|
|
g.flags[n] = FontReader.GetByte(); |
|
|
|
|
if ((g.flags[n].and(0x08)).toInt() != 0x00) { |
|
|
|
|
for (m in FontReader.GetByte() downTo 1) { |
|
|
|
|
g.flags[++n] = g.flags[n - 1] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
++n |
|
|
|
|
} |
|
|
|
|
++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() |
|
|
|
|
// 获取轮廓点描述x轴相对值 |
|
|
|
|
g.xCoordinates = ArrayList(flagLength) |
|
|
|
|
for (m in 0 until flagLength) { |
|
|
|
|
val same = if ((g.flags[m].and(0x10)).toInt() != 0) 1 else -1 |
|
|
|
|
if ((g.flags[m].and(0x02)).toInt() != 0) { |
|
|
|
|
g.xCoordinates[m] = (same * FontReader.GetByte()).toShort() |
|
|
|
|
} else { |
|
|
|
|
g.xCoordinates[m] = if (same == 1) 0 else FontReader.ReadInt16() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// 获取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() |
|
|
|
|
// 获取轮廓点描述y轴相对值 |
|
|
|
|
g.yCoordinates = ArrayList(flagLength) |
|
|
|
|
for (m in 0 until flagLength) { |
|
|
|
|
val same = if ((g.flags[m].and(0x20)).toInt() != 0) 1 else -1 |
|
|
|
|
if ((g.flags[m].and(0x04)).toInt() != 0) { |
|
|
|
|
g.yCoordinates[n] = (same * FontReader.GetByte()).toShort() |
|
|
|
|
} else { |
|
|
|
|
g.yCoordinates[n] = if (same == 1) 0 else FontReader.ReadInt16() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
/* |
|
|
|
|
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() |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
/* |
|
|
|
|
// 相对坐标转绝对坐标 (因不绘制字体,这里用不上) |
|
|
|
|
for(m in 1 until flagLength) |
|
|
|
|
{ |
|
|
|
|
g.xCoordinates[m] = (g.xCoordinates[m] + g.xCoordinates[m - 1]).toShort(); |
|
|
|
|
g.yCoordinates[m] = (g.yCoordinates[m] + g.yCoordinates[m - 1]).toShort(); |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
glyf.add(g) |
|
|
|
|
} else { |
|
|
|
|
// 复合字体暂不处理,以后用到再加 |
|
|
|
|
glyf.add(g); |
|
|
|
|
} else { |
|
|
|
|
// 复合字体暂未使用 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 建立Unicode&Glyf映射表 |
|
|
|
|
for (i in 0..130000) { |
|
|
|
|
val gid = GetGlyfIndex(i).toInt() |
|
|
|
|
if (gid == 0) continue; |
|
|
|
|
if (unicodeMap.containsKey(gid)) continue |
|
|
|
|
val thisGlyf = ArrayList<Short>() |
|
|
|
|
thisGlyf.addAll(glyf[gid].xCoordinates) |
|
|
|
|
thisGlyf.addAll(glyf[gid].yCoordinates) |
|
|
|
|
unicodeMap[i] = thisGlyf; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 获取字体信息(默认获取字体名称) |
|
|
|
|
* @see <a href="https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids">获取详情</a> |
|
|
|
|
* 获取字体信息 (默认获取字体名称,索引位1) |
|
|
|
|
*/ |
|
|
|
|
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) |
|
|
|
|
} |
|
|
|
|
fun GetNameById(nameId: Int = 1): String { |
|
|
|
|
for (Temp in directorys) { |
|
|
|
|
if (Temp.tag != "name") continue; |
|
|
|
|
FontReader.Index = Temp.offset.toInt(); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
for (record in name.records) { |
|
|
|
|
if (record.nameID.toInt() != nameId) continue; |
|
|
|
|
FontReader.Index = FontReader.Index + (name.stringOffset + record.offset).toInt(); |
|
|
|
|
return FontReader.ReadStrings(record.length.toInt(), if (record.platformID.toInt() == 1) Charsets.UTF_8 else Charsets.UTF_16BE); |
|
|
|
|
} |
|
|
|
|
return "error" |
|
|
|
|
return "error"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var pps = arrayListOf<Pair<UShort, UShort>>(Pair(3u, 10u), Pair(0u, 4u), Pair(3u, 1u), Pair(1u, 0u), Pair(0u, 3u), Pair(0u, 1u)) |
|
|
|
|
/** |
|
|
|
|
* 获取字体轮廓 (fontCode:十进制Unicode字符编码) |
|
|
|
|
*/ |
|
|
|
|
fun GetGlyf(fontCode: Int): ArrayList<Short> { |
|
|
|
|
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 |
|
|
|
|
* 使用Unicode值查找轮廓索引 |
|
|
|
|
*/ |
|
|
|
|
private fun GetGlyfIndex(code: Int): Long { |
|
|
|
|
var fmtKey: UInt = 0u; |
|
|
|
|
for (record in cmap.records) { |
|
|
|
|
for (item in pps) { |
|
|
|
|
if ((item.first == record.platformID) && (item.second == record.encodingID)) { |
|
|
|
|
fmtKey = record.offset; |
|
|
|
|
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 |
|
|
|
|
if (fmtKey > 0u) break; |
|
|
|
|
} |
|
|
|
|
if (fmtKey == 0u) return 0; |
|
|
|
|
|
|
|
|
|
var glyfID: Long = 0; |
|
|
|
|
if (cmap.tables[fmtKey] is CmapFormat0) { |
|
|
|
|
val tab = cmap.tables[fmtKey] as CmapFormat0 |
|
|
|
|
if (code >= tab.glyphIdArray.size) glyfID = 0; |
|
|
|
|
else glyfID = tab.glyphIdArray[code].toLong(); |
|
|
|
|
} else if (cmap.tables[fmtKey] is CmapFormat4) { |
|
|
|
|
val tab = cmap.tables[fmtKey] as CmapFormat4 |
|
|
|
|
if (code > tab.endCode.last().toInt()) return 0; |
|
|
|
|
// 二分法查找数值索引 |
|
|
|
|
var start = 0 |
|
|
|
|
var middle: Int |
|
|
|
|
var end = tab.endCode.size - 1; |
|
|
|
|
while (start + 1 != end) { |
|
|
|
|
middle = (start + end) / 2; |
|
|
|
|
if (tab.endCode[middle] <= code.toUInt()) start = middle; |
|
|
|
|
else end = middle; |
|
|
|
|
} |
|
|
|
|
if (tab.endCode[start] < code.toUInt()) ++start; |
|
|
|
|
if (code.toUInt() < tab.startCode[start]) return 0; |
|
|
|
|
glyfID = if (tab.idRangeOffset[start].toInt() != 0) { |
|
|
|
|
tab.glyphIdArray[code - tab.startCode[start].toInt() + (tab.idRangeOffset[start].toInt() / 2) - (tab.idRangeOffset.size - start)].toLong(); |
|
|
|
|
} else (code + tab.idDelta[start]).toLong(); |
|
|
|
|
glyfID = glyfID.and(0xFFFF) |
|
|
|
|
} else if (cmap.tables[fmtKey] is CmapFormat6) { |
|
|
|
|
val tab = cmap.tables[fmtKey] as CmapFormat6 |
|
|
|
|
val index = code - tab.firstCode.toInt(); |
|
|
|
|
glyfID = if (index < 0 || index >= tab.glyphIdArray.size) 0 else tab.glyphIdArray[index].toLong() |
|
|
|
|
} else if (cmap.tables[fmtKey] is CmapFormat12) { |
|
|
|
|
val tab = (cmap.tables[fmtKey] as CmapFormat12); |
|
|
|
|
if (code > tab.groups.last().second.toInt()) return 0; |
|
|
|
|
// 二分法查找数值索引 |
|
|
|
|
var start = 0 |
|
|
|
|
var middle: Int |
|
|
|
|
var end = tab.groups.size - 1; |
|
|
|
|
while (start + 1 != end) { |
|
|
|
|
middle = (start + end) / 2; |
|
|
|
|
if (tab.groups[middle].first.toInt() <= code) start = middle; |
|
|
|
|
else end = middle; |
|
|
|
|
} |
|
|
|
|
} 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() |
|
|
|
|
if (tab.groups[start].first.toInt() <= code && code <= tab.groups[start].second.toInt()) { |
|
|
|
|
glyfID = (tab.groups[start].third.toInt() + code - tab.groups[start].first.toInt()).toLong(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return glyfID |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
val glyph = ArrayList<Short>() |
|
|
|
|
if (fontIndex > -1) { |
|
|
|
|
// 将字体的轮廓坐标合并到一起返回,方便使用 |
|
|
|
|
glyph.addAll(glyf[fontIndex].xCoordinates) |
|
|
|
|
glyph.addAll(glyf[fontIndex].yCoordinates) |
|
|
|
|
/** |
|
|
|
|
* 使用轮廓数据获取Unicode值 |
|
|
|
|
*/ |
|
|
|
|
public fun GetCodeByGlyf(inputGlyf:List<Short>): Int { |
|
|
|
|
var unicodeVal = 0; |
|
|
|
|
if(inputGlyf.isEmpty()) return 0; |
|
|
|
|
for(g in unicodeMap) |
|
|
|
|
{ |
|
|
|
|
if (inputGlyf.size != g.value.size) continue; |
|
|
|
|
var isFound = true; |
|
|
|
|
for(i in inputGlyf.indices) |
|
|
|
|
{ |
|
|
|
|
if (inputGlyf[i] != g.value[i]) { |
|
|
|
|
isFound = false; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (isFound) unicodeVal = g.key; |
|
|
|
|
} |
|
|
|
|
return glyph |
|
|
|
|
return unicodeVal; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* 获取字体轮廓 (fontCode:单个String字符) |
|
|
|
|
/** |
|
|
|
|
* 使用Unicode值获取轮廓数据 |
|
|
|
|
*/ |
|
|
|
|
fun GetGlyf(fontCode: String): ArrayList<Short> { |
|
|
|
|
return GetGlyf(fontCode[0].toInt()) |
|
|
|
|
public fun GetGlyfByCode(code:Int): ArrayList<Short> |
|
|
|
|
{ |
|
|
|
|
if(code <= 0) return ArrayList() |
|
|
|
|
return unicodeMap.getOrDefault(code, ArrayList()) |
|
|
|
|
} |
|
|
|
|
} |