commit
f5c42ae158
@ -1,599 +0,0 @@ |
|||||||
package io.legado.app.model.analyzeRule; |
|
||||||
|
|
||||||
import org.apache.commons.lang3.tuple.Pair; |
|
||||||
import org.apache.commons.lang3.tuple.Triple; |
|
||||||
|
|
||||||
import java.nio.charset.Charset; |
|
||||||
import java.nio.charset.StandardCharsets; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.LinkedList; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
|
|
||||||
public class QueryTTF { |
|
||||||
private class Header { |
|
||||||
public int majorVersion; |
|
||||||
public int minorVersion; |
|
||||||
public int numOfTables; |
|
||||||
public int searchRange; |
|
||||||
public int entrySelector; |
|
||||||
public int rangeShift; |
|
||||||
} |
|
||||||
|
|
||||||
private class Directory { |
|
||||||
public String tag; // table name
|
|
||||||
public int checkSum; // Check sum
|
|
||||||
public int offset; // Offset from beginning of file
|
|
||||||
public int length; // length of the table in bytes
|
|
||||||
} |
|
||||||
|
|
||||||
private class NameLayout { |
|
||||||
public int format; |
|
||||||
public int count; |
|
||||||
public int stringOffset; |
|
||||||
public List<NameRecord> records = new LinkedList<>(); |
|
||||||
} |
|
||||||
|
|
||||||
private class NameRecord { |
|
||||||
public int platformID; // 平台标识符<0:Unicode, 1:Mac, 2:ISO, 3:Windows, 4:Custom>
|
|
||||||
public int encodingID; // 编码标识符
|
|
||||||
public int languageID; // 语言标识符
|
|
||||||
public int nameID; // 名称标识符
|
|
||||||
public int length; // 名称字符串的长度
|
|
||||||
public int offset; // 名称字符串相对于stringOffset的字节偏移量
|
|
||||||
} |
|
||||||
|
|
||||||
private class HeadLayout { |
|
||||||
public int majorVersion; |
|
||||||
public int minorVersion; |
|
||||||
public int fontRevision; |
|
||||||
public int checkSumAdjustment; |
|
||||||
public int magicNumber; |
|
||||||
public int flags; |
|
||||||
public int unitsPerEm; |
|
||||||
public long created; |
|
||||||
public long modified; |
|
||||||
public short xMin; |
|
||||||
public short yMin; |
|
||||||
public short xMax; |
|
||||||
public short yMax; |
|
||||||
public int macStyle; |
|
||||||
public int lowestRecPPEM; |
|
||||||
public short fontDirectionHint; |
|
||||||
public short indexToLocFormat; // <0:loca是2字节数组, 1:loca是4字节数组>
|
|
||||||
public short glyphDataFormat; |
|
||||||
} |
|
||||||
|
|
||||||
private class MaxpLayout { |
|
||||||
public int majorVersion; |
|
||||||
public int minorVersion; |
|
||||||
public int numGlyphs; // 字体中的字形数量
|
|
||||||
public int maxPoints; |
|
||||||
public int maxContours; |
|
||||||
public int maxCompositePoints; |
|
||||||
public int maxCompositeContours; |
|
||||||
public int maxZones; |
|
||||||
public int maxTwilightPoints; |
|
||||||
public int maxStorage; |
|
||||||
public int maxFunctionDefs; |
|
||||||
public int maxInstructionDefs; |
|
||||||
public int maxStackElements; |
|
||||||
public int maxSizeOfInstructions; |
|
||||||
public int maxComponentElements; |
|
||||||
public int maxComponentDepth; |
|
||||||
} |
|
||||||
|
|
||||||
private class CmapLayout { |
|
||||||
public int version; |
|
||||||
public int numTables; |
|
||||||
public List<CmapRecord> records = new LinkedList<>(); |
|
||||||
public Map<Integer, CmapFormat> tables = new HashMap<>(); |
|
||||||
} |
|
||||||
|
|
||||||
private class CmapRecord { |
|
||||||
public int platformID; |
|
||||||
public int encodingID; |
|
||||||
public int offset; |
|
||||||
} |
|
||||||
|
|
||||||
private class CmapFormat { |
|
||||||
public int format; |
|
||||||
public int length; |
|
||||||
public int language; |
|
||||||
public byte[] glyphIdArray; |
|
||||||
} |
|
||||||
|
|
||||||
private class CmapFormat4 extends CmapFormat { |
|
||||||
public int segCountX2; |
|
||||||
public int searchRange; |
|
||||||
public int entrySelector; |
|
||||||
public int rangeShift; |
|
||||||
public int[] endCode; |
|
||||||
public int reservedPad; |
|
||||||
public int[] startCode; |
|
||||||
public short[] idDelta; |
|
||||||
public int[] idRangeOffset; |
|
||||||
public int[] glyphIdArray; |
|
||||||
} |
|
||||||
|
|
||||||
private class CmapFormat6 extends CmapFormat { |
|
||||||
public int firstCode; |
|
||||||
public int entryCount; |
|
||||||
public int[] glyphIdArray; |
|
||||||
} |
|
||||||
|
|
||||||
private class CmapFormat12 extends CmapFormat { |
|
||||||
public int reserved; |
|
||||||
public int length; |
|
||||||
public int language; |
|
||||||
public int numGroups; |
|
||||||
public List<Triple<Integer, Integer, Integer>> groups; |
|
||||||
} |
|
||||||
|
|
||||||
private class GlyfLayout { |
|
||||||
public short numberOfContours; // 非负值为简单字型,负值为符合字型
|
|
||||||
public short xMin; |
|
||||||
public short yMin; |
|
||||||
public short xMax; |
|
||||||
public short yMax; |
|
||||||
public int[] endPtsOfContours; // length=numberOfContours
|
|
||||||
public int instructionLength; |
|
||||||
public byte[] instructions; // length=instructionLength
|
|
||||||
public byte[] flags; |
|
||||||
public short[] xCoordinates; // length = flags.length
|
|
||||||
public short[] yCoordinates; // length = flags.length
|
|
||||||
} |
|
||||||
|
|
||||||
private class ByteArrayReader { |
|
||||||
public int Index; |
|
||||||
public byte[] Buffer; |
|
||||||
|
|
||||||
public ByteArrayReader(byte[] buffer, int index) { |
|
||||||
Buffer = buffer; |
|
||||||
Index = index; |
|
||||||
} |
|
||||||
|
|
||||||
public long ReadUIntX(long len) { |
|
||||||
long result = 0; |
|
||||||
for (long i = 0; i < len; ++i) { |
|
||||||
result <<= 8; |
|
||||||
result |= Buffer[Index++] & 0xFF; |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
public long ReadUInt64() { |
|
||||||
return ReadUIntX(8); |
|
||||||
} |
|
||||||
|
|
||||||
public int ReadUInt32() { |
|
||||||
return (int) ReadUIntX(4); |
|
||||||
} |
|
||||||
|
|
||||||
public int ReadUInt16() { |
|
||||||
return (int) ReadUIntX(2); |
|
||||||
} |
|
||||||
|
|
||||||
public short ReadInt16() { |
|
||||||
return (short) ReadUIntX(2); |
|
||||||
} |
|
||||||
|
|
||||||
public short ReadUInt8() { |
|
||||||
return (short) ReadUIntX(1); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public String ReadStrings(int len, Charset charset) { |
|
||||||
byte[] result = len > 0 ? new byte[len] : null; |
|
||||||
for (int i = 0; i < len; ++i) result[i] = Buffer[Index++]; |
|
||||||
return new String(result, charset); |
|
||||||
} |
|
||||||
|
|
||||||
public byte GetByte() { |
|
||||||
return Buffer[Index++]; |
|
||||||
} |
|
||||||
|
|
||||||
public byte[] GetBytes(int len) { |
|
||||||
byte[] result = len > 0 ? new byte[len] : null; |
|
||||||
for (int i = 0; i < len; ++i) result[i] = Buffer[Index++]; |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
public int[] GetUInt16Array(int len) { |
|
||||||
int[] result = len > 0 ? new int[len] : null; |
|
||||||
for (int i = 0; i < len; ++i) result[i] = ReadUInt16(); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
public short[] GetInt16Array(int len) { |
|
||||||
short[] result = len > 0 ? new short[len] : null; |
|
||||||
for (int i = 0; i < len; ++i) result[i] = ReadInt16(); |
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private final ByteArrayReader FontReader; |
|
||||||
private final Header FileHeader = new Header(); |
|
||||||
private final List<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<Integer, Integer>[] 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 (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); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,633 @@ |
|||||||
|
package io.legado.app.model.analyzeRule |
||||||
|
|
||||||
|
import org.apache.commons.lang3.tuple.Pair |
||||||
|
import org.apache.commons.lang3.tuple.Triple |
||||||
|
import java.nio.charset.Charset |
||||||
|
import java.nio.charset.StandardCharsets |
||||||
|
import java.util.* |
||||||
|
import kotlin.experimental.and |
||||||
|
|
||||||
|
class QueryTTF(buffer: ByteArray) { |
||||||
|
private class Header( |
||||||
|
var majorVersion: Int = 0, |
||||||
|
var minorVersion: Int = 0, |
||||||
|
var numOfTables: Int = 0, |
||||||
|
var searchRange: Int = 0, |
||||||
|
var entrySelector: Int = 0, |
||||||
|
var rangeShift: Int = 0, |
||||||
|
) |
||||||
|
|
||||||
|
private class Directory { |
||||||
|
lateinit var tag: String // table name |
||||||
|
var checkSum: Int = 0 // Check sum |
||||||
|
var offset: Int = 0 // Offset from beginning of file |
||||||
|
var length: Int = 0 // length of the table in bytes |
||||||
|
} |
||||||
|
|
||||||
|
private class NameLayout( |
||||||
|
var format: Int = 0, |
||||||
|
var count: Int = 0, |
||||||
|
var stringOffset: Int = 0, |
||||||
|
var records: MutableList<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 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue