diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index 04f3c7744..aa6865aed 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -279,8 +279,8 @@ interface JsExtensions { val contentArray = text.toCharArray() contentArray.forEachIndexed { index, s -> val oldCode = s.toInt() - if (font1.InLimit(s)) { - val code = font2.GetCodeByGlyf(font1.GetGlyfByCode(oldCode)) + if (font1.inLimit(s)) { + val code = font2.getCodeByGlyf(font1.getGlyfByCode(oldCode)) if (code != 0) contentArray[index] = code.toChar() } } diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.java b/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.java deleted file mode 100644 index b7c47d7f1..000000000 --- a/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.java +++ /dev/null @@ -1,599 +0,0 @@ -package io.legado.app.model.analyzeRule; - -import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.tuple.Triple; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -public class QueryTTF { - private class Header { - public int majorVersion; - public int minorVersion; - public int numOfTables; - public int searchRange; - public int entrySelector; - public int rangeShift; - } - - private class Directory { - public String tag; // table name - public int checkSum; // Check sum - public int offset; // Offset from beginning of file - public int length; // length of the table in bytes - } - - private class NameLayout { - public int format; - public int count; - public int stringOffset; - public List records = new LinkedList<>(); - } - - private class NameRecord { - public int platformID; // 平台标识符<0:Unicode, 1:Mac, 2:ISO, 3:Windows, 4:Custom> - public int encodingID; // 编码标识符 - public int languageID; // 语言标识符 - public int nameID; // 名称标识符 - public int length; // 名称字符串的长度 - public int offset; // 名称字符串相对于stringOffset的字节偏移量 - } - - private class HeadLayout { - public int majorVersion; - public int minorVersion; - public int fontRevision; - public int checkSumAdjustment; - public int magicNumber; - public int flags; - public int unitsPerEm; - public long created; - public long modified; - public short xMin; - public short yMin; - public short xMax; - public short yMax; - public int macStyle; - public int lowestRecPPEM; - public short fontDirectionHint; - public short indexToLocFormat; // <0:loca是2字节数组, 1:loca是4字节数组> - public short glyphDataFormat; - } - - private class MaxpLayout { - public int majorVersion; - public int minorVersion; - public int numGlyphs; // 字体中的字形数量 - public int maxPoints; - public int maxContours; - public int maxCompositePoints; - public int maxCompositeContours; - public int maxZones; - public int maxTwilightPoints; - public int maxStorage; - public int maxFunctionDefs; - public int maxInstructionDefs; - public int maxStackElements; - public int maxSizeOfInstructions; - public int maxComponentElements; - public int maxComponentDepth; - } - - private class CmapLayout { - public int version; - public int numTables; - public List records = new LinkedList<>(); - public Map tables = new HashMap<>(); - } - - private class CmapRecord { - public int platformID; - public int encodingID; - public int offset; - } - - private class CmapFormat { - public int format; - public int length; - public int language; - public byte[] glyphIdArray; - } - - private class CmapFormat4 extends CmapFormat { - public int segCountX2; - public int searchRange; - public int entrySelector; - public int rangeShift; - public int[] endCode; - public int reservedPad; - public int[] startCode; - public short[] idDelta; - public int[] idRangeOffset; - public int[] glyphIdArray; - } - - private class CmapFormat6 extends CmapFormat { - public int firstCode; - public int entryCount; - public int[] glyphIdArray; - } - - private class CmapFormat12 extends CmapFormat { - public int reserved; - public int length; - public int language; - public int numGroups; - public List> groups; - } - - private class GlyfLayout { - public short numberOfContours; // 非负值为简单字型,负值为符合字型 - public short xMin; - public short yMin; - public short xMax; - public short yMax; - public int[] endPtsOfContours; // length=numberOfContours - public int instructionLength; - public byte[] instructions; // length=instructionLength - public byte[] flags; - public short[] xCoordinates; // length = flags.length - public short[] yCoordinates; // length = flags.length - } - - private class ByteArrayReader { - public int Index; - public byte[] Buffer; - - public ByteArrayReader(byte[] buffer, int index) { - Buffer = buffer; - Index = index; - } - - public long ReadUIntX(long len) { - long result = 0; - for (long i = 0; i < len; ++i) { - result <<= 8; - result |= Buffer[Index++] & 0xFF; - } - return result; - } - - public long ReadUInt64() { - return ReadUIntX(8); - } - - public int ReadUInt32() { - return (int) ReadUIntX(4); - } - - public int ReadUInt16() { - return (int) ReadUIntX(2); - } - - public short ReadInt16() { - return (short) ReadUIntX(2); - } - - public short ReadUInt8() { - return (short) ReadUIntX(1); - } - - - public String ReadStrings(int len, Charset charset) { - byte[] result = len > 0 ? new byte[len] : null; - for (int i = 0; i < len; ++i) result[i] = Buffer[Index++]; - return new String(result, charset); - } - - public byte GetByte() { - return Buffer[Index++]; - } - - public byte[] GetBytes(int len) { - byte[] result = len > 0 ? new byte[len] : null; - for (int i = 0; i < len; ++i) result[i] = Buffer[Index++]; - return result; - } - - public int[] GetUInt16Array(int len) { - int[] result = len > 0 ? new int[len] : null; - for (int i = 0; i < len; ++i) result[i] = ReadUInt16(); - return result; - } - - public short[] GetInt16Array(int len) { - short[] result = len > 0 ? new short[len] : null; - for (int i = 0; i < len; ++i) result[i] = ReadInt16(); - return result; - } - } - - private final ByteArrayReader FontReader; - private final Header FileHeader = new Header(); - private final List Directorys = new LinkedList<>(); - private final NameLayout Name = new NameLayout(); - private final HeadLayout Head = new HeadLayout(); - private final MaxpLayout Maxp = new MaxpLayout(); - private final List Loca = new LinkedList<>(); - private final CmapLayout Cmap = new CmapLayout(); - private final List Glyf = new LinkedList<>(); - private final Pair[] pps = new Pair[]{ - Pair.of(3, 10), - Pair.of(0, 4), - Pair.of(3, 1), - Pair.of(1, 0), - Pair.of(0, 3), - Pair.of(0, 1) - }; - - public final Map CodeToGlyph = new HashMap<>(); - public final Map GlyphToCode = new HashMap<>(); - private int LimitMix = 0; - private int LimitMax = 0; - - /** - * 构造函数 - * - * @param buffer 传入TTF字体二进制数组 - */ - public QueryTTF(byte[] buffer) { - FontReader = new ByteArrayReader(buffer, 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 (int i = 0; i < FileHeader.numOfTables; ++i) { - Directory d = new Directory(); - d.tag = FontReader.ReadStrings(4, StandardCharsets.US_ASCII); - d.checkSum = FontReader.ReadUInt32(); - d.offset = FontReader.ReadUInt32(); - d.length = FontReader.ReadUInt32(); - Directorys.add(d); - } - // 解析表 name (字体信息,包含版权、名称、作者等...) - for (Directory Temp : Directorys) { - if (Temp.tag.equals("name")) { - FontReader.Index = Temp.offset; - Name.format = FontReader.ReadUInt16(); - Name.count = FontReader.ReadUInt16(); - Name.stringOffset = FontReader.ReadUInt16(); - for (int i = 0; i < Name.count; ++i) { - NameRecord record = new 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) - for (Directory Temp : Directorys) { - if (Temp.tag.equals("head")) { - FontReader.Index = Temp.offset; - 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) - for (Directory Temp : Directorys) { - if (Temp.tag.equals("maxp")) { - FontReader.Index = Temp.offset; - 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 (轮廓数据偏移地址表) - for (Directory Temp : Directorys) { - if (Temp.tag.equals("loca")) { - FontReader.Index = Temp.offset; - int offset = Head.indexToLocFormat == 0 ? 2 : 4; - for (long i = 0; i < Temp.length; i += offset) { - Loca.add(offset == 2 ? FontReader.ReadUInt16() << 1 : FontReader.ReadUInt32()); - } - } - } - // 解析表 cmap (Unicode编码轮廓索引对照表) - for (Directory Temp : Directorys) { - if (Temp.tag.equals("cmap")) { - FontReader.Index = Temp.offset; - Cmap.version = FontReader.ReadUInt16(); - Cmap.numTables = FontReader.ReadUInt16(); - - for (int i = 0; i < Cmap.numTables; ++i) { - CmapRecord record = new CmapRecord(); - record.platformID = FontReader.ReadUInt16(); - record.encodingID = FontReader.ReadUInt16(); - record.offset = FontReader.ReadUInt32(); - Cmap.records.add(record); - } - for (int i = 0; i < Cmap.numTables; ++i) { - int fmtOffset = Cmap.records.get(i).offset; - FontReader.Index = Temp.offset + fmtOffset; - int EndIndex = FontReader.Index; - - int format = FontReader.ReadUInt16(); - if (Cmap.tables.containsKey(fmtOffset)) continue; - if (format == 0) { - CmapFormat f = new CmapFormat(); - f.format = format; - f.length = FontReader.ReadUInt16(); - f.language = FontReader.ReadUInt16(); - f.glyphIdArray = FontReader.GetBytes(f.length - 6); - Cmap.tables.put(fmtOffset, f); - } else if (format == 4) { - CmapFormat4 f = new CmapFormat4(); - f.format = format; - f.length = FontReader.ReadUInt16(); - f.language = FontReader.ReadUInt16(); - f.segCountX2 = FontReader.ReadUInt16(); - int segCount = f.segCountX2 >> 1; - f.searchRange = FontReader.ReadUInt16(); - f.entrySelector = FontReader.ReadUInt16(); - f.rangeShift = FontReader.ReadUInt16(); - f.endCode = FontReader.GetUInt16Array(segCount); - f.reservedPad = FontReader.ReadUInt16(); - f.startCode = FontReader.GetUInt16Array(segCount); - f.idDelta = FontReader.GetInt16Array(segCount); - f.idRangeOffset = FontReader.GetUInt16Array(segCount); - f.glyphIdArray = FontReader.GetUInt16Array((EndIndex + f.length - FontReader.Index) >> 1); - Cmap.tables.put(fmtOffset, f); - } else if (format == 6) { - CmapFormat6 f = new CmapFormat6(); - f.format = format; - f.length = FontReader.ReadUInt16(); - f.language = FontReader.ReadUInt16(); - f.firstCode = FontReader.ReadUInt16(); - f.entryCount = FontReader.ReadUInt16(); - f.glyphIdArray = FontReader.GetUInt16Array(f.entryCount); - Cmap.tables.put(fmtOffset, f); - } else if (format == 12) { - CmapFormat12 f = new CmapFormat12(); - f.format = format; - f.reserved = FontReader.ReadUInt16(); - f.length = FontReader.ReadUInt32(); - f.language = FontReader.ReadUInt32(); - f.numGroups = FontReader.ReadUInt32(); - f.groups = new ArrayList<>(f.numGroups); - for (int n = 0; n < f.numGroups; ++n) { - f.groups.add(Triple.of(FontReader.ReadUInt32(), FontReader.ReadUInt32(), FontReader.ReadUInt32())); - } - Cmap.tables.put(fmtOffset, f); - } - } - } - } - // 解析表 glyf (字体轮廓数据表) - for (Directory Temp : Directorys) { - if (Temp.tag.equals("glyf")) { - FontReader.Index = Temp.offset; - for (int i = 0; i < Maxp.numGlyphs; ++i) { - FontReader.Index = Temp.offset + Loca.get(i); - - 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); - } else { - // 复合字体暂未使用 - } - } - } - } - - // 建立Unicode&Glyph双向表 - for (int key = 0; key < 130000; ++key) { - if (key == 0xFF) key = 0x3400; - int gid = GetGlyfIndex(key); - if (gid == 0) 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 == 0) LimitMix = key; - LimitMax = key; - CodeToGlyph.put(key, val); - if (GlyphToCode.containsKey(val)) continue; - GlyphToCode.put(val, key); - } - } - - /** - * 获取字体信息 (1=字体名称) - * - * @param nameId 传入十进制字体信息索引 - * @return 返回查询结果字符串 - */ - public String GetNameById(int nameId) { - for (Directory Temp : Directorys) { - if (!Temp.tag.equals("name")) continue; - FontReader.Index = Temp.offset; - break; - } - for (NameRecord record : Name.records) { - if (record.nameID != nameId) continue; - FontReader.Index += Name.stringOffset + record.offset; - return FontReader.ReadStrings(record.length, record.platformID == 1 ? StandardCharsets.UTF_8 : StandardCharsets.UTF_16BE); - } - return "error"; - } - - /** - * 使用Unicode值查找轮廓索引 - * - * @param code 传入Unicode十进制值 - * @return 返回十进制轮廓索引 - */ - private int GetGlyfIndex(int code) { - if (code == 0) return 0; - int fmtKey = 0; - for (Pair item : pps) { - for (CmapRecord record : Cmap.records) { - if ((item.getLeft() == record.platformID) && (item.getRight() == record.encodingID)) { - fmtKey = record.offset; - break; - } - } - if (fmtKey > 0) break; - } - if (fmtKey == 0) return 0; - - int glyfID = 0; - CmapFormat table = Cmap.tables.get(fmtKey); - int fmt = table.format; - if (fmt == 0) { - if (code < table.glyphIdArray.length) glyfID = table.glyphIdArray[code] & 0xFF; - } else if (fmt == 4) { - CmapFormat4 tab = (CmapFormat4) table; - if (code > 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; - else end = middle; - } - if (tab.endCode[start] < code) ++start; - if (code < 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 &= 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]; - } else if (fmt == 12) { - CmapFormat12 tab = (CmapFormat12) table; - if (code > tab.groups.get(tab.numGroups - 1).getMiddle()) return 0; - // 二分法查找数值索引 - int start = 0, middle, end = tab.numGroups - 1; - while (start + 1 < end) { - middle = (start + end) / 2; - if (tab.groups.get(middle).getLeft() <= code) start = middle; - else end = middle; - } - if (tab.groups.get(start).getLeft() <= code && code <= tab.groups.get(start).getMiddle()) { - glyfID = tab.groups.get(start).getRight() + code - tab.groups.get(start).getLeft(); - } - } - return glyfID; - } - - /** - * 判断Unicode值是否在字体范围内 - * @param code 传入Unicode十进制值 - * @return 返回bool查询结果 - */ - public boolean InLimit(char code){ - return (LimitMix <= code) && (code < LimitMax); - } - - /** - * 使用Unicode值获取轮廓数据 - * - * @param key 传入Unicode十进制值 - * @return 返回轮廓数组的String值 - */ - public String GetGlyfByCode(int key) { - return CodeToGlyph.getOrDefault(key, ""); - } - - /** - * 使用轮廓数据获取Unicode值 - * - * @param val 传入轮廓数组的String值 - * @return 返回Unicode十进制值 - */ - public int GetCodeByGlyf(String val) { - return GlyphToCode.getOrDefault(val, 0); - } -} 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..0f7af2c52 --- /dev/null +++ b/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.kt @@ -0,0 +1,633 @@ +package io.legado.app.model.analyzeRule + +import org.apache.commons.lang3.tuple.Pair +import org.apache.commons.lang3.tuple.Triple +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets +import java.util.* +import kotlin.experimental.and + +class QueryTTF(buffer: ByteArray) { + private class Header( + var majorVersion: Int = 0, + var minorVersion: Int = 0, + var numOfTables: Int = 0, + var searchRange: Int = 0, + var entrySelector: Int = 0, + var rangeShift: Int = 0, + ) + + private class Directory { + lateinit var tag: String // table name + var checkSum: Int = 0 // Check sum + var offset: Int = 0 // Offset from beginning of file + var length: Int = 0 // length of the table in bytes + } + + private class NameLayout( + var format: Int = 0, + var count: Int = 0, + var stringOffset: Int = 0, + var records: MutableList = LinkedList(), + ) + + private class NameRecord( + var platformID: Int = 0, // 平台标识符<0:Unicode, 1:Mac, 2:ISO, 3:Windows, 4:Custom> + var encodingID: Int = 0, // 编码标识符 + var languageID: Int = 0, // 语言标识符 + var nameID: Int = 0, // 名称标识符 + var length: Int = 0,// 名称字符串的长度 + var offset: Int = 0, // 名称字符串相对于stringOffset的字节偏移量 + ) + + private class HeadLayout( + var majorVersion: Int = 0, + var minorVersion: Int = 0, + var fontRevision: Int = 0, + var checkSumAdjustment: Int = 0, + var magicNumber: Int = 0, + var flags: Int = 0, + var unitsPerEm: Int = 0, + var created: Long = 0, + var modified: Long = 0, + var xMin: Short = 0, + var yMin: Short = 0, + var xMax: Short = 0, + var yMax: Short = 0, + var macStyle: Int = 0, + var lowestRecPPEM: Int = 0, + var fontDirectionHint: Short = 0, + var indexToLocFormat: Short = 0, // <0:loca是2字节数组, 1:loca是4字节数组> + var glyphDataFormat: Short = 0, + ) + + private class MaxpLayout( + var majorVersion: Int = 0, + var minorVersion: Int = 0, + var numGlyphs: Int = 0, // 字体中的字形数量 + var maxPoints: Int = 0, + var maxContours: Int = 0, + var maxCompositePoints: Int = 0, + var maxCompositeContours: Int = 0, + var maxZones: Int = 0, + var maxTwilightPoints: Int = 0, + var maxStorage: Int = 0, + var maxFunctionDefs: Int = 0, + var maxInstructionDefs: Int = 0, + var maxStackElements: Int = 0, + var maxSizeOfInstructions: Int = 0, + var maxComponentElements: Int = 0, + var maxComponentDepth: Int = 0, + ) + + private class CmapLayout( + var version: Int = 0, + var numTables: Int = 0, + var records: MutableList = LinkedList(), + var tables: MutableMap = HashMap(), + ) + + private class CmapRecord( + var platformID: Int = 0, + var encodingID: Int = 0, + var offset: Int = 0, + ) + + private open class CmapFormat { + var format: Int = 0 + open var length: Int = 0 + open var language: Int = 0 + lateinit var glyphIdArray: IntArray + } + + private class CmapFormat4 : CmapFormat() { + var segCountX2: Int = 0 + var searchRange: Int = 0 + var entrySelector: Int = 0 + var rangeShift: Int = 0 + lateinit var endCode: IntArray + var reservedPad: Int = 0 + lateinit var startCode: IntArray + lateinit var idDelta: ShortArray + lateinit var idRangeOffset: IntArray +// override lateinit var glyphIdArray: IntArray + } + + private class CmapFormat6( + var firstCode: Int = 0, + var entryCount: Int = 0, + ) : CmapFormat() + + private class CmapFormat12( + var reserved: Int = 0, + override var length: Int = 0, + override var language: Int = 0, + var numGroups: Int = 0, + var groups: MutableList> + ) : CmapFormat() + + private class GlyfLayout( + var numberOfContours: Short = 0, // 非负值为简单字型,负值为符合字型 + var xMin: Short = 0, + var yMin: Short = 0, + var xMax: Short = 0, + var yMax: Short = 0, + var instructionLength: Int = 0 + ) { + lateinit var endPtsOfContours: IntArray // length=numberOfContours + lateinit var instructions: IntArray // length=instructionLength + lateinit var flags: ByteArray + lateinit var xCoordinates: ShortArray // length = flags.length + lateinit var yCoordinates: ShortArray // length = flags.length + } + + private class ByteArrayReader(var buffer: ByteArray, var index: Int) { + fun readUIntX(len: Long): Long { + var result: Long = 0 + for (i in 0 until len) { + result = result shl 8 + result = result or ((buffer[index++] and 0xFF.toByte()).toLong()) + } + return result + } + + fun readUInt64(): Long = readUIntX(8) + + fun readUInt32(): Int = readUIntX(4).toInt() + + fun readUInt16(): Int = readUIntX(2).toInt() + + fun readInt16(): Short = readUIntX(2).toShort() + + fun readUInt8(): Short = readUIntX(1).toShort() + + fun readStrings(len: Int, charset: Charset): String { + val result = ByteArray(len) + for (i in 0 until len) { + result[i] = buffer[index++] + } + return String(result, charset) + } + + fun getByte() = buffer[index++] + + fun getBytes(len: Int): IntArray { + val result = IntArray(len) + for (i in 0 until len) { + result[i] = buffer[index++].toInt() + } + return result + } + + fun getUInt16Array(len: Int): IntArray { + val result = IntArray(len) + for (i in 0 until len) { + result[i] = readUInt16() + } + return result + } + + fun getInt16Array(len: Int): ShortArray { + val result = ShortArray(len) + for (i in 0 until len) { + result[i] = readInt16() + } + return result + } + } + + private val fontReader: ByteArrayReader = ByteArrayReader(buffer, 0) + private val directories: MutableList = LinkedList() + private val name = NameLayout() + private val cmap = CmapLayout() + private val pps: Array> = arrayOf( + Pair.of(3, 10), + Pair.of(0, 4), + Pair.of(3, 1), + Pair.of(1, 0), + Pair.of(0, 3), + Pair.of(0, 1) + ) + private val codeToGlyph: MutableMap = HashMap() + private val glyphToCode: MutableMap = HashMap() + private var limitMix = 0 + private var limitMax = 0 + + /** + * 获取字体信息 (1=字体名称) + * + * @param nameId 传入十进制字体信息索引 + * @return 返回查询结果字符串 + */ + fun getNameById(nameId: Int): String { + for (temp in directories) { + if ("name" != temp.tag) { + continue + } + fontReader.index = temp.offset + break + } + for (record in name.records) { + if (record.nameID != nameId) { + continue + } + fontReader.index += name.stringOffset + record.offset + return fontReader.readStrings(record.length, if (record.platformID == 1) StandardCharsets.UTF_8 else StandardCharsets.UTF_16BE) + } + return "error" + } + + /** + * 使用Unicode值查找轮廓索引 + * + * @param code 传入Unicode十进制值 + * @return 返回十进制轮廓索引 + */ + private fun getGlyfIndex(code: Int): Int { + if (code == 0) { + return 0 + } + var fmtKey = 0 + for (item in pps) { + for (record in cmap.records) { + if (item.left == record.platformID && item.right == record.encodingID) { + fmtKey = record.offset + break + } + } + if (fmtKey > 0) { + break + } + } + if (fmtKey == 0) { + return 0 + } + var glyfID = 0 + val table = cmap.tables[fmtKey] + when (table?.format) { + 0 -> { + if (code < table.glyphIdArray.size) { + glyfID = table.glyphIdArray[code] and 0xFF + } + } + 4 -> { + val tab = table as CmapFormat4 + if (code > tab.endCode[tab.endCode.size - 1]) { + 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) { + start = middle + } else { + end = middle + } + } + if (tab.endCode[start] < code) { + ++start + } + if (code < tab.startCode[start]) { + return 0 + } + glyfID = if (tab.idRangeOffset[start] != 0) { + tab.glyphIdArray[code - tab.startCode[start] + (tab.idRangeOffset[start] shr 1) - (tab.idRangeOffset.size - start)] + } else { + code + tab.idDelta[start] + } + glyfID = glyfID and 0xFFFF + } + 6 -> { + val tab = table as CmapFormat6 + val index = code - tab.firstCode + glyfID = if (index < 0 || index >= tab.glyphIdArray.size) { + 0 + } else { + tab.glyphIdArray[index] + } + } + 12 -> { + val tab = table as CmapFormat12 + if (code > tab.groups[tab.numGroups - 1].middle) { + return 0 + } + // 二分法查找数值索引 + var start = 0 + var middle: Int + var end = tab.numGroups - 1 + while (start + 1 < end) { + middle = (start + end) / 2 + if (tab.groups[middle].left <= code) { + start = middle + } else { + end = middle + } + } + if (tab.groups[start].left <= code && code <= tab.groups[start].middle) { + glyfID = tab.groups[start].right + code - tab.groups[start].left + } + } + } + return glyfID + } + + /** + * 判断Unicode值是否在字体范围内 + * @param code 传入Unicode十进制值 + * @return 返回bool查询结果 + */ + fun inLimit(code: Char): Boolean = code.toInt() in limitMix until limitMax + + /** + * 使用Unicode值获取轮廓数据 + * + * @param key 传入Unicode十进制值 + * @return 返回轮廓数组的String值 + */ + fun getGlyfByCode(key: Int): String = codeToGlyph.getOrDefault(key, "") + + /** + * 使用轮廓数据获取Unicode值 + * + * @param val 传入轮廓数组的String值 + * @return 返回Unicode十进制值 + */ + fun getCodeByGlyf(`val`: String): Int = glyphToCode.getOrDefault(`val`, 0) + + /** + * 构造函数 buffer 传入TTF字体二进制数组 + */ + init { + // 获取文件头 + val fileHeader = Header() + 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) { + val d = Directory() + d.tag = fontReader.readStrings(4, StandardCharsets.US_ASCII) + d.checkSum = fontReader.readUInt32() + d.offset = fontReader.readUInt32() + d.length = fontReader.readUInt32() + directories.add(d) + } + // 解析表 name (字体信息,包含版权、名称、作者等...) + for (temp in directories) { + if ("name" == temp.tag) { + fontReader.index = temp.offset + name.format = fontReader.readUInt16() + name.count = fontReader.readUInt16() + name.stringOffset = fontReader.readUInt16() + for (i in 0 until name.count) { + 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) + val head = HeadLayout() + for (temp in directories) { + if ("head" == temp.tag) { + fontReader.index = temp.offset + 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) + val maxp = MaxpLayout() + for (temp in directories) { + if ("maxp" == temp.tag) { + fontReader.index = temp.offset + 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 (轮廓数据偏移地址表) + val loca: MutableList = LinkedList() + for (temp in directories) { + if ("loca" == temp.tag) { + fontReader.index = temp.offset + val offset = if (head.indexToLocFormat == 0.toShort()) 2 else 4 + var i: Long = 0 + while (i < temp.length) { + loca.add(if (offset == 2) fontReader.readUInt16() shl 1 else fontReader.readUInt32()) + i += offset + } + } + } + // 解析表 cmap (Unicode编码轮廓索引对照表) + for (temp in directories) { + if ("cmap" == temp.tag) { + fontReader.index = temp.offset + cmap.version = fontReader.readUInt16() + cmap.numTables = fontReader.readUInt16() + for (i in 0 until cmap.numTables) { + 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) { + val fmtOffset = cmap.records[i].offset + fontReader.index = temp.offset + fmtOffset + val endIndex = fontReader.index + val format = fontReader.readUInt16() + if (cmap.tables.containsKey(fmtOffset)) { + continue + } + when (format) { + 0 -> { + val f = CmapFormat() + f.format = format + f.length = fontReader.readUInt16() + f.language = fontReader.readUInt16() + f.glyphIdArray = fontReader.getBytes(f.length - 6) + cmap.tables[fmtOffset] = f + } + 4 -> { + val f = CmapFormat4() + f.format = format + f.length = fontReader.readUInt16() + f.language = fontReader.readUInt16() + f.segCountX2 = fontReader.readUInt16() + val segCount = f.segCountX2 shr 1 + f.searchRange = fontReader.readUInt16() + f.entrySelector = fontReader.readUInt16() + f.rangeShift = fontReader.readUInt16() + f.endCode = fontReader.getUInt16Array(segCount) + f.reservedPad = fontReader.readUInt16() + f.startCode = fontReader.getUInt16Array(segCount) + f.idDelta = fontReader.getInt16Array(segCount) + f.idRangeOffset = fontReader.getUInt16Array(segCount) + f.glyphIdArray = fontReader.getUInt16Array(endIndex + f.length - fontReader.index shr 1) + cmap.tables[fmtOffset] = f + } + 6 -> { + val f = CmapFormat6() + f.format = format + f.length = fontReader.readUInt16() + f.language = fontReader.readUInt16() + f.firstCode = fontReader.readUInt16() + f.entryCount = fontReader.readUInt16() + f.glyphIdArray = fontReader.getUInt16Array(f.entryCount) + cmap.tables[fmtOffset] = f + } + 12 -> { + val f = CmapFormat12( + reserved = fontReader.readUInt16(), + length = fontReader.readUInt32(), + language = fontReader.readUInt32(), + numGroups = fontReader.readUInt32(), + groups = ArrayList(fontReader.readUInt32()) + ) + f.format = format + for (n in 0 until f.numGroups) { + (f.groups as ArrayList>).add(Triple.of(fontReader.readUInt32(), fontReader.readUInt32(), fontReader.readUInt32())) + } + cmap.tables[fmtOffset] = f + } + } + } + } + } + // 解析表 glyf (字体轮廓数据表) + val glyf: MutableList = LinkedList() + for (temp in directories) { + if ("glyf" == temp.tag) { + fontReader.index = temp.offset + for (i in 0 until maxp.numGlyphs) { + fontReader.index = temp.offset + loca[i] + val numberOfContours = fontReader.readInt16() + if (numberOfContours > 0) { + val g = 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.toInt()) + g.instructionLength = fontReader.readUInt16() + g.instructions = fontReader.getBytes(g.instructionLength) + val flagLength = g.endPtsOfContours[g.endPtsOfContours.size - 1] + 1 + // 获取轮廓点描述标志 + g.flags = ByteArray(flagLength).apply { + var n = 0 + while (n < flagLength) { + this[n] = fontReader.getByte() + if ((this[n] and 0x08) != 0x00.toByte()) { + for (m in fontReader.readUInt8() downTo 1) { + this[++n] = this[n - 1] + } + } + ++n + } + } + // 获取轮廓点描述x轴相对值 + g.xCoordinates = ShortArray(flagLength) + for (n in 0 until flagLength) { + val same = (if (g.flags[n] and 0x10 != 0.toByte()) 1 else -1).toShort() + if (g.flags[n] and 0x02 != 0.toByte()) { + g.xCoordinates[n] = (same * fontReader.readUInt8()).toShort() + } else { + g.xCoordinates[n] = if (same.toInt() == 1) 0.toShort() else fontReader.readInt16() + } + } + // 获取轮廓点描述y轴相对值 + g.yCoordinates = ShortArray(flagLength) + for (n in 0 until flagLength) { + val same = (if (g.flags[n] and 0x20 != 0.toByte()) 1 else -1).toShort() + if (g.flags[n] and 0x04 != 0.toByte()) { + g.yCoordinates[n] = (same * fontReader.readUInt8()).toShort() + } else { + g.yCoordinates[n] = if (same.toInt() == 1) 0.toShort() else fontReader.readInt16() + } + } + // 相对坐标转绝对坐标 +// for (int n = 1; n < flagLength; ++n) { +// xCoordinates[n] += xCoordinates[n - 1]; +// yCoordinates[n] += yCoordinates[n - 1]; +// } + glyf.add(g) + } //else情况:复合字体暂未使用 + } + } + } + + // 建立Unicode&Glyph双向表 + var key = 0 + while (key < 130000) { + if (key == 0xFF) { + key = 0x3400 + } + val gid = getGlyfIndex(key) + if (gid == 0) { + ++key + continue + } + val sb = StringBuilder() + // 字型数据转String,方便存HashMap + for (b in glyf[gid].xCoordinates) { + sb.append(b.toInt()) + } + for (b in glyf[gid].yCoordinates) { + sb.append(b.toInt()) + } + val `val` = sb.toString() + if (limitMix == 0) { + limitMix = key + } + limitMax = key + codeToGlyph[key] = `val` + if (glyphToCode.containsKey(`val`)) { + ++key + continue + } + glyphToCode[`val`] = key + ++key + } + } +} \ No newline at end of file