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