commit
						645abe3315
					
				@ -0,0 +1,6 @@ | 
				
			|||||||
 | 
					# 替换管理界面帮助 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* 替换规则是用来替换正文内容的一种规则 | 
				
			||||||
 | 
					  * 菜单可以新建和导入规则 | 
				
			||||||
 | 
					  * 可以拖动排序 | 
				
			||||||
 | 
					  * 可以选择操作 | 
				
			||||||
@ -0,0 +1,27 @@ | 
				
			|||||||
 | 
					package io.legado.app.constant | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.provider.Settings | 
				
			||||||
 | 
					import splitties.init.appCtx | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					val androidId: String by lazy { | 
				
			||||||
 | 
					    Settings.System.getString(appCtx.contentResolver, Settings.Secure.ANDROID_ID) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					val appInfo: AppInfo by lazy { | 
				
			||||||
 | 
					    val appInfo = AppInfo() | 
				
			||||||
 | 
					    appCtx.packageManager.getPackageInfo(appCtx.packageName, 0)?.let { | 
				
			||||||
 | 
					        appInfo.versionName = it.versionName | 
				
			||||||
 | 
					        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { | 
				
			||||||
 | 
					            appInfo.versionCode = it.longVersionCode | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            @Suppress("DEPRECATION") | 
				
			||||||
 | 
					            appInfo.versionCode = it.versionCode.toLong() | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    appInfo | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class AppInfo( | 
				
			||||||
 | 
					    var versionCode: Long = 0L, | 
				
			||||||
 | 
					    var versionName: String = "" | 
				
			||||||
 | 
					) | 
				
			||||||
@ -0,0 +1,600 @@ | 
				
			|||||||
 | 
					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 static class Header { | 
				
			||||||
 | 
					        public int majorVersion; | 
				
			||||||
 | 
					        public int minorVersion; | 
				
			||||||
 | 
					        public int numOfTables; | 
				
			||||||
 | 
					        public int searchRange; | 
				
			||||||
 | 
					        public int entrySelector; | 
				
			||||||
 | 
					        public int rangeShift; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static 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 static class NameLayout { | 
				
			||||||
 | 
					        public int format; | 
				
			||||||
 | 
					        public int count; | 
				
			||||||
 | 
					        public int stringOffset; | 
				
			||||||
 | 
					        public List<NameRecord> records = new LinkedList<>(); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static 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 static 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 static 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<CmapRecord> records = new LinkedList<>(); | 
				
			||||||
 | 
					        public Map<Integer, CmapFormat> tables = new HashMap<>(); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static class CmapRecord { | 
				
			||||||
 | 
					        public int platformID; | 
				
			||||||
 | 
					        public int encodingID; | 
				
			||||||
 | 
					        public int offset; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static class CmapFormat { | 
				
			||||||
 | 
					        public int format; | 
				
			||||||
 | 
					        public int length; | 
				
			||||||
 | 
					        public int language; | 
				
			||||||
 | 
					        public byte[] glyphIdArray; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static 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 static class CmapFormat6 extends CmapFormat { | 
				
			||||||
 | 
					        public int firstCode; | 
				
			||||||
 | 
					        public int entryCount; | 
				
			||||||
 | 
					        public int[] glyphIdArray; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static class CmapFormat12 extends CmapFormat { | 
				
			||||||
 | 
					        public int reserved; | 
				
			||||||
 | 
					        public int length; | 
				
			||||||
 | 
					        public int language; | 
				
			||||||
 | 
					        public int numGroups; | 
				
			||||||
 | 
					        public List<Triple<Integer, Integer, Integer>> groups; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static 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 static 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<Directory> Directorys = new LinkedList<>(); | 
				
			||||||
 | 
					    private final NameLayout Name = new NameLayout(); | 
				
			||||||
 | 
					    private final HeadLayout head = new HeadLayout(); | 
				
			||||||
 | 
					    private final MaxpLayout maxp = new MaxpLayout(); | 
				
			||||||
 | 
					    private final List<Integer> loca = new LinkedList<>(); | 
				
			||||||
 | 
					    private final CmapLayout Cmap = new CmapLayout(); | 
				
			||||||
 | 
					    private final List<GlyfLayout> glyf = new LinkedList<>(); | 
				
			||||||
 | 
					    private final 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<Integer, String> CodeToGlyph = new HashMap<>(); | 
				
			||||||
 | 
					    public final Map<String, Integer> 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 (@SuppressWarnings("unchecked") Pair<Integer, Integer> 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); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,633 +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.* | 
					 | 
				
			||||||
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<NameRecord> = 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<CmapRecord> = LinkedList(), | 
					 | 
				
			||||||
            var tables: MutableMap<Int, CmapFormat> = 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<Triple<Int, Int, Int>> | 
					 | 
				
			||||||
    ) : 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<Directory> = LinkedList() | 
					 | 
				
			||||||
    private val name = NameLayout() | 
					 | 
				
			||||||
    private val cmap = CmapLayout() | 
					 | 
				
			||||||
    private val pps: Array<Pair<Int, Int>> = 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<Int, String> = HashMap() | 
					 | 
				
			||||||
    private val glyphToCode: MutableMap<String, Int> = 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<Int> = 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<Triple<Int, Int, Int>>).add(Triple.of(fontReader.readUInt32(), fontReader.readUInt32(), fontReader.readUInt32())) | 
					 | 
				
			||||||
                            } | 
					 | 
				
			||||||
                            cmap.tables[fmtOffset] = f | 
					 | 
				
			||||||
                        } | 
					 | 
				
			||||||
                    } | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        // 解析表 glyf (字体轮廓数据表) | 
					 | 
				
			||||||
        val glyf: MutableList<GlyfLayout> = 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 | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue