|
|
@@ -1,17 +1,21 @@
|
|
|
package io.legado.app.model.analyzeRule;
|
|
|
|
|
|
-import org.apache.commons.lang3.tuple.Pair;
|
|
|
-import org.apache.commons.lang3.tuple.Triple;
|
|
|
+import android.util.Log;
|
|
|
|
|
|
-import java.nio.charset.Charset;
|
|
|
-import java.nio.charset.StandardCharsets;
|
|
|
-import java.util.ArrayList;
|
|
|
+import java.nio.ByteBuffer;
|
|
|
+import java.nio.ByteOrder;
|
|
|
import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.util.LinkedList;
|
|
|
import java.util.List;
|
|
|
-import java.util.Map;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
+import java.util.concurrent.ExecutorService;
|
|
|
+import java.util.concurrent.Executors;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
-@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
|
|
+@SuppressWarnings({"FieldCanBeLocal", "StatementWithEmptyBody", "unused"})
|
|
|
public class QueryTTF {
|
|
|
private static class Header {
|
|
|
public int majorVersion;
|
|
|
@@ -129,7 +133,7 @@ public class QueryTTF {
|
|
|
public int length;
|
|
|
public int language;
|
|
|
public int numGroups;
|
|
|
- public List<Triple<Integer, Integer, Integer>> groups;
|
|
|
+ public List<int[]> groups;
|
|
|
}
|
|
|
|
|
|
private static class GlyfLayout {
|
|
|
@@ -147,103 +151,313 @@ public class QueryTTF {
|
|
|
}
|
|
|
|
|
|
private static class ByteArrayReader {
|
|
|
- public int index;
|
|
|
- public byte[] buffer;
|
|
|
+ private final byte[] buffer;
|
|
|
+ public final ByteBuffer byteBuffer;
|
|
|
|
|
|
public ByteArrayReader(byte[] buffer, int index) {
|
|
|
this.buffer = buffer;
|
|
|
- this.index = index;
|
|
|
+ this.byteBuffer = ByteBuffer.wrap(buffer);
|
|
|
+ this.byteBuffer.order(ByteOrder.BIG_ENDIAN); // 设置为大端模式
|
|
|
+ this.byteBuffer.position(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 void position(int index) {
|
|
|
+ byteBuffer.position(index); // 设置起始索引
|
|
|
+ }
|
|
|
+
|
|
|
+ public int position() {
|
|
|
+ return byteBuffer.position();
|
|
|
}
|
|
|
|
|
|
+
|
|
|
public long ReadUInt64() {
|
|
|
- return ReadUIntX(8);
|
|
|
+ return byteBuffer.getLong();
|
|
|
}
|
|
|
|
|
|
public int ReadUInt32() {
|
|
|
- return (int) ReadUIntX(4);
|
|
|
+ return byteBuffer.getInt();
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ReadInt32() {
|
|
|
+ return byteBuffer.getInt();
|
|
|
}
|
|
|
|
|
|
public int ReadUInt16() {
|
|
|
- return (int) ReadUIntX(2);
|
|
|
+ return byteBuffer.getShort() & 0xFFFF;
|
|
|
}
|
|
|
|
|
|
public short ReadInt16() {
|
|
|
- return (short) ReadUIntX(2);
|
|
|
+ return byteBuffer.getShort();
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
+ return (short) (byteBuffer.get() & 0xFF);
|
|
|
}
|
|
|
|
|
|
- public byte GetByte() {
|
|
|
- return buffer[index++];
|
|
|
+ public byte ReadInt8() {
|
|
|
+ return byteBuffer.get();
|
|
|
}
|
|
|
|
|
|
- public byte[] GetBytes(int len) {
|
|
|
- byte[] result = len > 0 ? new byte[len] : null;
|
|
|
- for (int i = 0; i < len; ++i) result[i] = buffer[index++];
|
|
|
+ public byte[] ReadByteArray(int len) {
|
|
|
+ if (len < 0) throw new IllegalArgumentException("Length must not be negative");
|
|
|
+ byte[] result = new byte[len];
|
|
|
+ byteBuffer.get(result);
|
|
|
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();
|
|
|
+ public short[] ReadInt16Array(int len) {
|
|
|
+ if (len < 0) throw new IllegalArgumentException("Length must not be negative");
|
|
|
+ var result = new short[len];
|
|
|
+ for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort();
|
|
|
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();
|
|
|
+ public int[] ReadUInt16Array(int len) {
|
|
|
+ if (len < 0) throw new IllegalArgumentException("Length must not be negative");
|
|
|
+ var result = new int[len];
|
|
|
+ for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort() & 0xFFFF;
|
|
|
return result;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private final ByteArrayReader fontReader;
|
|
|
private final Header fileHeader = new Header();
|
|
|
- private final List<Directory> directorys = new LinkedList<>();
|
|
|
+ private final Map<String, Directory> directorys = new HashMap<>();
|
|
|
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<String> glyf = new LinkedList<>();
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- 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)
|
|
|
+ private final ConcurrentHashMap<Integer, String> glyf = new ConcurrentHashMap<>();
|
|
|
+ private final int[][] pps = new int[][]{
|
|
|
+ {3, 10},
|
|
|
+ {0, 4},
|
|
|
+ {3, 1},
|
|
|
+ {1, 0},
|
|
|
+ {0, 3},
|
|
|
+ {0, 1}
|
|
|
};
|
|
|
|
|
|
public final Map<Integer, String> unicodeToGlyph = new HashMap<>();
|
|
|
public final Map<String, Integer> glyphToUnicode = new HashMap<>();
|
|
|
public final Map<Integer, Integer> unicodeToGlyphIndex = new HashMap<>();
|
|
|
|
|
|
+ private void readNameTable(byte[] buffer) {
|
|
|
+ var dataTable = Objects.requireNonNull(directorys.get("name"));
|
|
|
+ var reader = new ByteArrayReader(buffer, dataTable.offset);
|
|
|
+ name.format = reader.ReadUInt16();
|
|
|
+ name.count = reader.ReadUInt16();
|
|
|
+ name.stringOffset = reader.ReadUInt16();
|
|
|
+ for (int i = 0; i < name.count; ++i) {
|
|
|
+ NameRecord record = new NameRecord();
|
|
|
+ record.platformID = reader.ReadUInt16();
|
|
|
+ record.encodingID = reader.ReadUInt16();
|
|
|
+ record.languageID = reader.ReadUInt16();
|
|
|
+ record.nameID = reader.ReadUInt16();
|
|
|
+ record.length = reader.ReadUInt16();
|
|
|
+ record.offset = reader.ReadUInt16();
|
|
|
+ name.records.add(record);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void readHeadTable(byte[] buffer) {
|
|
|
+ var dataTable = Objects.requireNonNull(directorys.get("head"));
|
|
|
+ var reader = new ByteArrayReader(buffer, dataTable.offset);
|
|
|
+ head.majorVersion = reader.ReadUInt16();
|
|
|
+ head.minorVersion = reader.ReadUInt16();
|
|
|
+ head.fontRevision = reader.ReadUInt32();
|
|
|
+ head.checkSumAdjustment = reader.ReadUInt32();
|
|
|
+ head.magicNumber = reader.ReadUInt32();
|
|
|
+ head.flags = reader.ReadUInt16();
|
|
|
+ head.unitsPerEm = reader.ReadUInt16();
|
|
|
+ head.created = reader.ReadUInt64();
|
|
|
+ head.modified = reader.ReadUInt64();
|
|
|
+ head.xMin = reader.ReadInt16();
|
|
|
+ head.yMin = reader.ReadInt16();
|
|
|
+ head.xMax = reader.ReadInt16();
|
|
|
+ head.yMax = reader.ReadInt16();
|
|
|
+ head.macStyle = reader.ReadUInt16();
|
|
|
+ head.lowestRecPPEM = reader.ReadUInt16();
|
|
|
+ head.fontDirectionHint = reader.ReadInt16();
|
|
|
+ head.indexToLocFormat = reader.ReadInt16();
|
|
|
+ head.glyphDataFormat = reader.ReadInt16();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void readCmapTable(byte[] buffer) {
|
|
|
+ var dataTable = Objects.requireNonNull(directorys.get("cmap"));
|
|
|
+ var reader = new ByteArrayReader(buffer, dataTable.offset);
|
|
|
+ Cmap.version = reader.ReadUInt16();
|
|
|
+ Cmap.numTables = reader.ReadUInt16();
|
|
|
+ for (int i = 0; i < Cmap.numTables; ++i) {
|
|
|
+ CmapRecord record = new CmapRecord();
|
|
|
+ record.platformID = reader.ReadUInt16();
|
|
|
+ record.encodingID = reader.ReadUInt16();
|
|
|
+ record.offset = reader.ReadUInt32();
|
|
|
+ Cmap.records.add(record);
|
|
|
+ }
|
|
|
+ for (int i = 0; i < Cmap.numTables; ++i) {
|
|
|
+ int fmtOffset = Cmap.records.get(i).offset;
|
|
|
+ reader.position(dataTable.offset + fmtOffset);
|
|
|
+ int EndIndex = reader.position();
|
|
|
+
|
|
|
+ int format = reader.ReadUInt16();
|
|
|
+ if (Cmap.tables.containsKey(fmtOffset)) continue;
|
|
|
+ if (format == 0) {
|
|
|
+ CmapFormat f = new CmapFormat();
|
|
|
+ f.format = format;
|
|
|
+ f.length = reader.ReadUInt16();
|
|
|
+ f.language = reader.ReadUInt16();
|
|
|
+ f.glyphIdArray = reader.ReadByteArray(f.length - 6);
|
|
|
+ Cmap.tables.put(fmtOffset, f);
|
|
|
+ } else if (format == 4) {
|
|
|
+ CmapFormat4 f = new CmapFormat4();
|
|
|
+ f.format = format;
|
|
|
+ f.length = reader.ReadUInt16();
|
|
|
+ f.language = reader.ReadUInt16();
|
|
|
+ f.segCountX2 = reader.ReadUInt16();
|
|
|
+ int segCount = f.segCountX2 >> 1;
|
|
|
+ f.searchRange = reader.ReadUInt16();
|
|
|
+ f.entrySelector = reader.ReadUInt16();
|
|
|
+ f.rangeShift = reader.ReadUInt16();
|
|
|
+ f.endCode = reader.ReadUInt16Array(segCount);
|
|
|
+ f.reservedPad = reader.ReadUInt16();
|
|
|
+ f.startCode = reader.ReadUInt16Array(segCount);
|
|
|
+ f.idDelta = reader.ReadInt16Array(segCount);
|
|
|
+ f.idRangeOffset = reader.ReadUInt16Array(segCount);
|
|
|
+ f.glyphIdArray = reader.ReadUInt16Array((EndIndex + f.length - reader.position()) >> 1);
|
|
|
+ Cmap.tables.put(fmtOffset, f);
|
|
|
+ } else if (format == 6) {
|
|
|
+ CmapFormat6 f = new CmapFormat6();
|
|
|
+ f.format = format;
|
|
|
+ f.length = reader.ReadUInt16();
|
|
|
+ f.language = reader.ReadUInt16();
|
|
|
+ f.firstCode = reader.ReadUInt16();
|
|
|
+ f.entryCount = reader.ReadUInt16();
|
|
|
+ f.glyphIdArray = reader.ReadUInt16Array(f.entryCount);
|
|
|
+ Cmap.tables.put(fmtOffset, f);
|
|
|
+ } else if (format == 12) {
|
|
|
+ CmapFormat12 f = new CmapFormat12();
|
|
|
+ f.format = format;
|
|
|
+ f.reserved = reader.ReadUInt16();
|
|
|
+ f.length = reader.ReadUInt32();
|
|
|
+ f.language = reader.ReadUInt32();
|
|
|
+ f.numGroups = reader.ReadUInt32();
|
|
|
+ f.groups = new LinkedList<>();
|
|
|
+ for (int n = 0; n < f.numGroups; ++n) {
|
|
|
+ f.groups.add(new int[]{reader.ReadUInt32(), reader.ReadUInt32(), reader.ReadUInt32()});
|
|
|
+ }
|
|
|
+ Cmap.tables.put(fmtOffset, f);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void readLocaTable(byte[] buffer) {
|
|
|
+ var dataTable = Objects.requireNonNull(directorys.get("loca"));
|
|
|
+ var reader = new ByteArrayReader(buffer, dataTable.offset);
|
|
|
+ var locaTableSize = dataTable.length;
|
|
|
+ if (head.indexToLocFormat == 0) {
|
|
|
+ for (long i = 0; i < locaTableSize; i += 2) loca.add(reader.ReadUInt16());
|
|
|
+ } else {
|
|
|
+ for (long i = 0; i < locaTableSize; i += 4) loca.add(reader.ReadUInt32());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void readMaxpTable(byte[] buffer) {
|
|
|
+ var dataTable = Objects.requireNonNull(directorys.get("maxp"));
|
|
|
+ var reader = new ByteArrayReader(buffer, dataTable.offset);
|
|
|
+ maxp.majorVersion = reader.ReadUInt16();
|
|
|
+ maxp.minorVersion = reader.ReadUInt16();
|
|
|
+ maxp.numGlyphs = reader.ReadUInt16();
|
|
|
+ maxp.maxPoints = reader.ReadUInt16();
|
|
|
+ maxp.maxContours = reader.ReadUInt16();
|
|
|
+ maxp.maxCompositePoints = reader.ReadUInt16();
|
|
|
+ maxp.maxCompositeContours = reader.ReadUInt16();
|
|
|
+ maxp.maxZones = reader.ReadUInt16();
|
|
|
+ maxp.maxTwilightPoints = reader.ReadUInt16();
|
|
|
+ maxp.maxStorage = reader.ReadUInt16();
|
|
|
+ maxp.maxFunctionDefs = reader.ReadUInt16();
|
|
|
+ maxp.maxInstructionDefs = reader.ReadUInt16();
|
|
|
+ maxp.maxStackElements = reader.ReadUInt16();
|
|
|
+ maxp.maxSizeOfInstructions = reader.ReadUInt16();
|
|
|
+ maxp.maxComponentElements = reader.ReadUInt16();
|
|
|
+ maxp.maxComponentDepth = reader.ReadUInt16();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void readGlyfTable(byte[] buffer) {
|
|
|
+ var dataTable = Objects.requireNonNull(directorys.get("glyf"));
|
|
|
+ int glyfCount = maxp.numGlyphs;
|
|
|
+
|
|
|
+ ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
|
|
+ for (int i = 0; i < glyfCount; i++) {
|
|
|
+ final int index = i;
|
|
|
+ executor.submit(() -> {
|
|
|
+ var reader = new ByteArrayReader(buffer, dataTable.offset + loca.get(index));
|
|
|
+ int glyfNextIndex = index + 1 < glyfCount ? (dataTable.offset + loca.get(index + 1)) : (dataTable.offset + dataTable.length);
|
|
|
+ byte[] glyph;
|
|
|
+ short numberOfContours = reader.ReadInt16();
|
|
|
+ if (numberOfContours > 0) {
|
|
|
+ short g_xMin = reader.ReadInt16();
|
|
|
+ short g_yMin = reader.ReadInt16();
|
|
|
+ short g_xMax = reader.ReadInt16();
|
|
|
+ short g_yMax = reader.ReadInt16();
|
|
|
+ int[] endPtsOfContours = reader.ReadUInt16Array(numberOfContours);
|
|
|
+ int instructionLength = reader.ReadUInt16();
|
|
|
+ var instructions = reader.ReadByteArray(instructionLength);
|
|
|
+ int flagLength = endPtsOfContours[endPtsOfContours.length - 1] + 1;
|
|
|
+ // 获取轮廓点描述标志
|
|
|
+ var flags = new byte[flagLength];
|
|
|
+ for (int n = 0; n < flagLength; ++n) {
|
|
|
+ flags[n] = reader.ReadInt8();
|
|
|
+ if ((flags[n] & 0x08) != 0x00) {
|
|
|
+ for (int m = reader.ReadUInt8(); m > 0; --m) {
|
|
|
+ flags[++n] = flags[n - 1];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 获取轮廓点描述x,y相对值
|
|
|
+ ByteBuffer xyCoordinatesBuffer = ByteBuffer.allocate(flagLength * 4);
|
|
|
+ // 获取轮廓点描述x轴相对值
|
|
|
+ for (int n = 0; n < flagLength; ++n) {
|
|
|
+ short same = (short) ((flags[n] & 0x10) != 0 ? 1 : -1);
|
|
|
+ if ((flags[n] & 0x02) != 0) {
|
|
|
+ xyCoordinatesBuffer.putShort((short) (same * reader.ReadUInt8()));
|
|
|
+ } else {
|
|
|
+ xyCoordinatesBuffer.putShort(same == 1 ? (short) 0 : reader.ReadInt16());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 获取轮廓点描述y轴相对值
|
|
|
+ for (int n = 0; n < flagLength; ++n) {
|
|
|
+ short same = (short) ((flags[n] & 0x20) != 0 ? 1 : -1);
|
|
|
+ if ((flags[n] & 0x04) != 0) {
|
|
|
+ xyCoordinatesBuffer.putShort((short) (same * reader.ReadUInt8()));
|
|
|
+ } else {
|
|
|
+ xyCoordinatesBuffer.putShort(same == 1 ? (short) 0 : reader.ReadInt16());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 保存轮廓点描述x,y相对值ByteArray
|
|
|
+ glyph = xyCoordinatesBuffer.array();
|
|
|
+ } else {
|
|
|
+ // 复合字体未做详细处理
|
|
|
+ glyph = reader.ReadByteArray(glyfNextIndex - (reader.position() - 2));
|
|
|
+ }
|
|
|
+ glyf.put(index, getHexFromBytes(glyph));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ executor.shutdown();
|
|
|
+ try {
|
|
|
+ boolean b = executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Log.e("queryTTF", "glyf表解析出错: " + e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 构造函数
|
|
|
*
|
|
|
* @param buffer 传入TTF字体二进制数组
|
|
|
*/
|
|
|
public QueryTTF(byte[] buffer) {
|
|
|
- fontReader = new ByteArrayReader(buffer, 0);
|
|
|
+ var fontReader = new ByteArrayReader(buffer, 0);
|
|
|
+ Log.i("[queryTTF]", "读文件头");
|
|
|
// 获取文件头
|
|
|
fileHeader.majorVersion = fontReader.ReadUInt16();
|
|
|
fileHeader.minorVersion = fontReader.ReadUInt16();
|
|
|
@@ -254,187 +468,29 @@ public class QueryTTF {
|
|
|
// 获取目录
|
|
|
for (int i = 0; i < fileHeader.numOfTables; ++i) {
|
|
|
Directory d = new Directory();
|
|
|
- d.tag = fontReader.ReadStrings(4, StandardCharsets.US_ASCII);
|
|
|
+ d.tag = new String(fontReader.ReadByteArray(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")) {
|
|
|
- int glyfCount = maxp.numGlyphs;
|
|
|
- for (int i = 0; i < glyfCount; ) {
|
|
|
- fontReader.index = Temp.offset + loca.get(i);
|
|
|
- ++i;
|
|
|
- int glyfNextIndex = i < glyfCount ? loca.get(i) : Temp.length;
|
|
|
-
|
|
|
- byte[] glyph;
|
|
|
- short numberOfContours = fontReader.ReadInt16();
|
|
|
- if (numberOfContours > 0) {
|
|
|
- short g_xMin = fontReader.ReadInt16();
|
|
|
- short g_yMin = fontReader.ReadInt16();
|
|
|
- short g_xMax = fontReader.ReadInt16();
|
|
|
- short g_yMax = fontReader.ReadInt16();
|
|
|
- int[] endPtsOfContours = fontReader.GetUInt16Array(numberOfContours);
|
|
|
- glyph = fontReader.GetBytes(glyfNextIndex - (fontReader.index - Temp.offset));
|
|
|
- } else {
|
|
|
- glyph = fontReader.GetBytes(glyfNextIndex - (fontReader.index - 2));
|
|
|
- }
|
|
|
- glyf.add(getHexFromBytes(glyph));
|
|
|
- }
|
|
|
- }
|
|
|
+ directorys.put(d.tag, d);
|
|
|
}
|
|
|
|
|
|
- // 建立Unicode&Glyph双向表
|
|
|
+ Log.i("[queryTTF]", "解析表 name"); // 字体信息,包含版权、名称、作者等...
|
|
|
+ readNameTable(buffer);
|
|
|
+ Log.i("[queryTTF]", "解析表 head"); // 获取 head.indexToLocFormat
|
|
|
+ readHeadTable(buffer);
|
|
|
+ Log.i("[queryTTF]", "解析表 cmap"); // Unicode编码->轮廓索引 对照表
|
|
|
+ readCmapTable(buffer);
|
|
|
+ Log.i("[queryTTF]", "解析表 loca"); // 轮廓数据偏移地址表
|
|
|
+ readLocaTable(buffer);
|
|
|
+ Log.i("[queryTTF]", "解析表 maxp"); // 获取 maxp.numGlyphs 字体轮廓数量
|
|
|
+ readMaxpTable(buffer);
|
|
|
+ Log.i("[queryTTF]", "解析表 glyf"); // 字体轮廓数据表,需要解析loca,maxp表后计算
|
|
|
+ readGlyfTable(buffer);
|
|
|
+
|
|
|
+ Log.i("[queryTTF]", "创建Unicode&Glyph映射表");
|
|
|
for (int key = 0; key < 130000; ++key) {
|
|
|
-// if (key == 0xFF) key = 0x3400;
|
|
|
- int gid = queryGlyfIndex(key);
|
|
|
+ Integer gid = queryGlyfIndex(key);
|
|
|
if (gid >= glyf.size()) continue;
|
|
|
unicodeToGlyphIndex.put(key, gid);
|
|
|
var val = glyf.get(gid);
|
|
|
@@ -442,27 +498,24 @@ public class QueryTTF {
|
|
|
if (glyphToUnicode.containsKey(val)) continue;
|
|
|
glyphToUnicode.put(val, key);
|
|
|
}
|
|
|
+ Log.i("[queryTTF]", "字体处理完成");
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取字体信息 (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";
|
|
|
- }
|
|
|
+// /**
|
|
|
+// * 获取字体信息 (1=字体名称)
|
|
|
+// *
|
|
|
+// * @param nameId 传入十进制字体信息索引
|
|
|
+// * @return 返回查询结果字符串
|
|
|
+// */
|
|
|
+// public String getNameById(int nameId) {
|
|
|
+// fontReader.index = Objects.requireNonNull(directorys.get("name")).offset;
|
|
|
+// 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值查找轮廓索引
|
|
|
@@ -472,10 +525,11 @@ public class QueryTTF {
|
|
|
*/
|
|
|
public int queryGlyfIndex(int unicode) {
|
|
|
if (unicode == 0) return 0;
|
|
|
+
|
|
|
int fmtKey = 0;
|
|
|
- for (Pair<Integer, Integer> item : pps) {
|
|
|
+ for (var item : pps) {
|
|
|
for (CmapRecord record : Cmap.records) {
|
|
|
- if ((item.getLeft() == record.platformID) && (item.getRight() == record.encodingID)) {
|
|
|
+ if ((item[0] == record.platformID) && (item[1] == record.encodingID)) {
|
|
|
fmtKey = record.offset;
|
|
|
break;
|
|
|
}
|
|
|
@@ -512,10 +566,10 @@ public class QueryTTF {
|
|
|
if (0 <= index && index < tab.glyphIdArray.length) glyfID = tab.glyphIdArray[index];
|
|
|
} else if (fmt == 12) {
|
|
|
CmapFormat12 tab = (CmapFormat12) table;
|
|
|
- if (unicode > tab.groups.get(tab.numGroups - 1).getMiddle()) return 0;
|
|
|
+ if (unicode > tab.groups.get(tab.numGroups - 1)[1]) return 0;
|
|
|
for (int i = 0; i < tab.numGroups; i++) {
|
|
|
- if (tab.groups.get(i).getLeft() <= unicode && unicode <= tab.groups.get(i).getMiddle()) {
|
|
|
- glyfID = tab.groups.get(i).getRight() + unicode - tab.groups.get(i).getLeft();
|
|
|
+ if (tab.groups.get(i)[0] <= unicode && unicode <= tab.groups.get(i)[1]) {
|
|
|
+ glyfID = tab.groups.get(i)[2] + unicode - tab.groups.get(i)[0];
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
@@ -564,14 +618,8 @@ public class QueryTTF {
|
|
|
*/
|
|
|
public String getHexFromBytes(byte[] glyph) {
|
|
|
if (glyph == null) return "";
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- for (byte b : glyph) {
|
|
|
- String hex = Integer.toHexString(b);
|
|
|
- if (hex.length() == 1) {
|
|
|
- sb.append("0");//当16进制为个位数时,在前面补0
|
|
|
- }
|
|
|
- sb.append(hex);//将16进制加入字符串
|
|
|
- }
|
|
|
- return sb.toString().toUpperCase();
|
|
|
+ StringBuilder hexString = new StringBuilder();
|
|
|
+ for (byte b : glyph) hexString.append(String.format("%02X", b));
|
|
|
+ return hexString.toString();
|
|
|
}
|
|
|
}
|