优化字体解析效率

This commit is contained in:
Antecer 2024-04-30 15:44:02 +08:00
parent fd3d96fa74
commit de801d61c1
2 changed files with 114 additions and 104 deletions

View File

@ -803,7 +803,7 @@ interface JsExtensions : JsEncodeUtils {
if (code != 0) { if (code != 0) {
contentArray[index] = code.toChar().toString() contentArray[index] = code.toChar().toString()
} }
if (glyf == "" && filter) { if (glyf.equals("") && filter) {
// 删除轮廓数据为空的字符 // 删除轮廓数据为空的字符
contentArray[index] = "" contentArray[index] = ""
} }

View File

@ -4,6 +4,7 @@ import android.util.Log;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -97,9 +98,9 @@ public class QueryTTF {
} }
private static class CmapRecord { private static class CmapRecord {
public int platformID; public int platformID; // UInt16
public int encodingID; public int platformSpecificID; // UInt16
public int offset; public int offset; // UInt32
} }
private static class CmapFormat { private static class CmapFormat {
@ -150,12 +151,10 @@ public class QueryTTF {
public short[] yCoordinates; // length = flags.length public short[] yCoordinates; // length = flags.length
} }
private static class ByteArrayReader { private static class BufferReader {
private final byte[] buffer; private final ByteBuffer byteBuffer;
public final ByteBuffer byteBuffer;
public ByteArrayReader(byte[] buffer, int index) { public BufferReader(byte[] buffer, int index) {
this.buffer = buffer;
this.byteBuffer = ByteBuffer.wrap(buffer); this.byteBuffer = ByteBuffer.wrap(buffer);
this.byteBuffer.order(ByteOrder.BIG_ENDIAN); // 设置为大端模式 this.byteBuffer.order(ByteOrder.BIG_ENDIAN); // 设置为大端模式
this.byteBuffer.position(index); // 设置起始索引 this.byteBuffer.position(index); // 设置起始索引
@ -199,21 +198,21 @@ public class QueryTTF {
} }
public byte[] ReadByteArray(int len) { public byte[] ReadByteArray(int len) {
if (len < 0) throw new IllegalArgumentException("Length must not be negative"); if (len < 0) return null;
byte[] result = new byte[len]; byte[] result = new byte[len];
byteBuffer.get(result); byteBuffer.get(result);
return result; return result;
} }
public short[] ReadInt16Array(int len) { public short[] ReadInt16Array(int len) {
if (len < 0) throw new IllegalArgumentException("Length must not be negative"); if (len < 0) return null;
var result = new short[len]; var result = new short[len];
for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort(); for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort();
return result; return result;
} }
public int[] ReadUInt16Array(int len) { public int[] ReadUInt16Array(int len) {
if (len < 0) throw new IllegalArgumentException("Length must not be negative"); if (len < 0) return null;
var result = new int[len]; var result = new int[len];
for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort() & 0xFFFF; for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort() & 0xFFFF;
return result; return result;
@ -227,23 +226,12 @@ public class QueryTTF {
private final MaxpLayout maxp = new MaxpLayout(); private final MaxpLayout maxp = new MaxpLayout();
private final List<Integer> loca = new LinkedList<>(); private final List<Integer> loca = new LinkedList<>();
private final CmapLayout Cmap = new CmapLayout(); private final CmapLayout Cmap = new CmapLayout();
private final ConcurrentHashMap<Integer, String> glyf = new ConcurrentHashMap<>(); private String[] glyf;
private final int[][] pps = new int[][]{ private final int[][] pps = new int[][]{{3, 10}, {0, 4}, {3, 1}, {1, 0}, {0, 3}, {0, 1}};
{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) { private void readNameTable(byte[] buffer) {
var dataTable = Objects.requireNonNull(directorys.get("name")); var dataTable = Objects.requireNonNull(directorys.get("name"));
var reader = new ByteArrayReader(buffer, dataTable.offset); var reader = new BufferReader(buffer, dataTable.offset);
name.format = reader.ReadUInt16(); name.format = reader.ReadUInt16();
name.count = reader.ReadUInt16(); name.count = reader.ReadUInt16();
name.stringOffset = reader.ReadUInt16(); name.stringOffset = reader.ReadUInt16();
@ -261,7 +249,7 @@ public class QueryTTF {
private void readHeadTable(byte[] buffer) { private void readHeadTable(byte[] buffer) {
var dataTable = Objects.requireNonNull(directorys.get("head")); var dataTable = Objects.requireNonNull(directorys.get("head"));
var reader = new ByteArrayReader(buffer, dataTable.offset); var reader = new BufferReader(buffer, dataTable.offset);
head.majorVersion = reader.ReadUInt16(); head.majorVersion = reader.ReadUInt16();
head.minorVersion = reader.ReadUInt16(); head.minorVersion = reader.ReadUInt16();
head.fontRevision = reader.ReadUInt32(); head.fontRevision = reader.ReadUInt32();
@ -284,13 +272,13 @@ public class QueryTTF {
private void readCmapTable(byte[] buffer) { private void readCmapTable(byte[] buffer) {
var dataTable = Objects.requireNonNull(directorys.get("cmap")); var dataTable = Objects.requireNonNull(directorys.get("cmap"));
var reader = new ByteArrayReader(buffer, dataTable.offset); var reader = new BufferReader(buffer, dataTable.offset);
Cmap.version = reader.ReadUInt16(); Cmap.version = reader.ReadUInt16();
Cmap.numTables = reader.ReadUInt16(); Cmap.numTables = reader.ReadUInt16();
for (int i = 0; i < Cmap.numTables; ++i) { for (int i = 0; i < Cmap.numTables; ++i) {
CmapRecord record = new CmapRecord(); CmapRecord record = new CmapRecord();
record.platformID = reader.ReadUInt16(); record.platformID = reader.ReadUInt16();
record.encodingID = reader.ReadUInt16(); record.platformSpecificID = reader.ReadUInt16();
record.offset = reader.ReadUInt32(); record.offset = reader.ReadUInt32();
Cmap.records.add(record); Cmap.records.add(record);
} }
@ -352,7 +340,7 @@ public class QueryTTF {
private void readLocaTable(byte[] buffer) { private void readLocaTable(byte[] buffer) {
var dataTable = Objects.requireNonNull(directorys.get("loca")); var dataTable = Objects.requireNonNull(directorys.get("loca"));
var reader = new ByteArrayReader(buffer, dataTable.offset); var reader = new BufferReader(buffer, dataTable.offset);
var locaTableSize = dataTable.length; var locaTableSize = dataTable.length;
if (head.indexToLocFormat == 0) { if (head.indexToLocFormat == 0) {
for (long i = 0; i < locaTableSize; i += 2) loca.add(reader.ReadUInt16()); for (long i = 0; i < locaTableSize; i += 2) loca.add(reader.ReadUInt16());
@ -363,7 +351,7 @@ public class QueryTTF {
private void readMaxpTable(byte[] buffer) { private void readMaxpTable(byte[] buffer) {
var dataTable = Objects.requireNonNull(directorys.get("maxp")); var dataTable = Objects.requireNonNull(directorys.get("maxp"));
var reader = new ByteArrayReader(buffer, dataTable.offset); var reader = new BufferReader(buffer, dataTable.offset);
maxp.majorVersion = reader.ReadUInt16(); maxp.majorVersion = reader.ReadUInt16();
maxp.minorVersion = reader.ReadUInt16(); maxp.minorVersion = reader.ReadUInt16();
maxp.numGlyphs = reader.ReadUInt16(); maxp.numGlyphs = reader.ReadUInt16();
@ -385,100 +373,86 @@ public class QueryTTF {
private void readGlyfTable(byte[] buffer) { private void readGlyfTable(byte[] buffer) {
var dataTable = Objects.requireNonNull(directorys.get("glyf")); var dataTable = Objects.requireNonNull(directorys.get("glyf"));
int glyfCount = maxp.numGlyphs; int glyfCount = maxp.numGlyphs;
glyf = new String[glyfCount];
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); int coreCount = Runtime.getRuntime().availableProcessors();
for (int i = 0; i < glyfCount; i++) { ExecutorService executor = Executors.newFixedThreadPool(coreCount);
final int index = i; int sliceSize = (coreCount < glyfCount) ? (glyfCount / coreCount) : glyfCount;
final var reader = new ByteArrayReader(buffer, dataTable.offset + loca.get(index)); for (int blockOffset = 0; blockOffset < glyfCount; blockOffset += sliceSize) {
final int inclusive = blockOffset;
final int exclusive = Math.min(blockOffset + sliceSize, glyfCount);
executor.submit(() -> { executor.submit(() -> {
int glyfNextIndex = index + 1 < glyfCount ? (dataTable.offset + loca.get(index + 1)) : (dataTable.offset + dataTable.length); var reader = new BufferReader(buffer, 0);
byte[] glyph; for (int index = inclusive; index < exclusive; index++) {
short numberOfContours = reader.ReadInt16(); int offset = dataTable.offset + loca.get(index);
if (numberOfContours > 0) { int glyfLength = (index + 1 < glyfCount ? loca.get(index + 1) : dataTable.offset + dataTable.length) - loca.get(index);
short g_xMin = reader.ReadInt16(); reader.position(offset);
short g_yMin = reader.ReadInt16(); byte[] glyph;
short g_xMax = reader.ReadInt16(); short numberOfContours = reader.ReadInt16();
short g_yMax = reader.ReadInt16(); if (numberOfContours > 0) {
int[] endPtsOfContours = reader.ReadUInt16Array(numberOfContours); short g_xMin = reader.ReadInt16();
int instructionLength = reader.ReadUInt16(); short g_yMin = reader.ReadInt16();
var instructions = reader.ReadByteArray(instructionLength); short g_xMax = reader.ReadInt16();
int flagLength = endPtsOfContours[endPtsOfContours.length - 1] + 1; short g_yMax = reader.ReadInt16();
// 获取轮廓点描述标志 int[] endPtsOfContours = reader.ReadUInt16Array(numberOfContours);
var flags = new byte[flagLength]; int instructionLength = reader.ReadUInt16();
for (int n = 0; n < flagLength; ++n) { var instructions = reader.ReadByteArray(instructionLength);
flags[n] = reader.ReadInt8(); int flagLength = endPtsOfContours[endPtsOfContours.length - 1] + 1;
if ((flags[n] & 0x08) != 0x00) { // 获取轮廓点描述标志
for (int m = reader.ReadUInt8(); m > 0; --m) { var flags = new byte[flagLength];
flags[++n] = flags[n - 1]; 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相对值
// 获取轮廓点描述x,y相对值 ByteBuffer xyCoordinatesBuffer = ByteBuffer.allocate(flagLength * 4);
ByteBuffer xyCoordinatesBuffer = ByteBuffer.allocate(flagLength * 4); // 获取轮廓点描述x轴相对值
// 获取轮廓点描述x轴相对值 for (int n = 0; n < flagLength; ++n) {
for (int n = 0; n < flagLength; ++n) { short same = (short) ((flags[n] & 0x10) != 0 ? 1 : -1);
short same = (short) ((flags[n] & 0x10) != 0 ? 1 : -1); if ((flags[n] & 0x02) != 0) {
if ((flags[n] & 0x02) != 0) { xyCoordinatesBuffer.putShort((short) (same * reader.ReadUInt8()));
xyCoordinatesBuffer.putShort((short) (same * reader.ReadUInt8())); } else {
} else { xyCoordinatesBuffer.putShort(same == 1 ? (short) 0 : reader.ReadInt16());
xyCoordinatesBuffer.putShort(same == 1 ? (short) 0 : reader.ReadInt16()); }
} }
} // 获取轮廓点描述y轴相对值
// 获取轮廓点描述y轴相对值 for (int n = 0; n < flagLength; ++n) {
for (int n = 0; n < flagLength; ++n) { short same = (short) ((flags[n] & 0x20) != 0 ? 1 : -1);
short same = (short) ((flags[n] & 0x20) != 0 ? 1 : -1); if ((flags[n] & 0x04) != 0) {
if ((flags[n] & 0x04) != 0) { xyCoordinatesBuffer.putShort((short) (same * reader.ReadUInt8()));
xyCoordinatesBuffer.putShort((short) (same * reader.ReadUInt8())); } else {
} else { xyCoordinatesBuffer.putShort(same == 1 ? (short) 0 : reader.ReadInt16());
xyCoordinatesBuffer.putShort(same == 1 ? (short) 0 : reader.ReadInt16()); }
} }
// 保存轮廓点描述x,y相对值ByteArray
glyph = xyCoordinatesBuffer.array();
} else {
// TODO: 处理复合字形
glyph = reader.ReadByteArray(glyfLength - 2);
} }
// 保存轮廓点描述x,y相对值ByteArray glyf[index] = getHexFromBytes(glyph);
glyph = xyCoordinatesBuffer.array();
} else {
// 复合字体未做详细处理
glyph = reader.ReadByteArray(glyfNextIndex - (reader.position() - 2));
} }
glyf.put(index, getHexFromBytes(glyph));
}); });
} }
executor.shutdown(); executor.shutdown();
try { try {
boolean b = executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); boolean b = executor.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Log.e("queryTTF", "glyf表解析出错: " + e); Log.e("queryTTF", "glyf表解析出错: " + e);
} }
} }
private void makeHashTable() {
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
for (int i = 0; i < 130000; ++i) {
final int key = i;
executor.submit(() -> {
Integer gid = queryGlyfIndex(key);
if (gid < glyf.size()) {
unicodeToGlyphIndex.put(key, gid);
var val = glyf.get(gid);
unicodeToGlyph.put(key, val);
if (!glyphToUnicode.containsKey(val)) glyphToUnicode.put(val, key);
}
});
}
executor.shutdown();
try {
boolean b = executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e("queryTTF", "建立Unicode&Glyph映射表出错: " + e);
}
}
/** /**
* 构造函数 * 构造函数
* *
* @param buffer 传入TTF字体二进制数组 * @param buffer 传入TTF字体二进制数组
*/ */
public QueryTTF(byte[] buffer) { public QueryTTF(final byte[] buffer) {
var fontReader = new ByteArrayReader(buffer, 0); var fontReader = new BufferReader(buffer, 0);
Log.i("queryTTF", "读文件头"); Log.i("queryTTF", "读文件头");
// 获取文件头 // 获取文件头
fileHeader.majorVersion = fontReader.ReadUInt16(); fileHeader.majorVersion = fontReader.ReadUInt16();
@ -514,6 +488,38 @@ public class QueryTTF {
Log.i("queryTTF", "字体处理完成"); Log.i("queryTTF", "字体处理完成");
} }
public final ConcurrentHashMap<Integer, String> unicodeToGlyph = new ConcurrentHashMap<>();
public final ConcurrentHashMap<String, Integer> glyphToUnicode = new ConcurrentHashMap<>();
public final ConcurrentHashMap<Integer, Integer> unicodeToGlyphIndex = new ConcurrentHashMap<>();
private void makeHashTable() {
int coreCount = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(coreCount);
int unicodeCount = 0xFFFF;
int sliceSize = (coreCount < unicodeCount) ? (unicodeCount / coreCount) : unicodeCount;
for (int blockOffset = 0; blockOffset < unicodeCount; blockOffset += sliceSize) {
final int inclusive = blockOffset;
final int exclusive = Math.min(blockOffset + sliceSize, unicodeCount);
executor.submit(() -> {
for (int index = inclusive; index < exclusive; index++) {
int gid = queryGlyfIndex(index);
if (gid < glyf.length) {
unicodeToGlyphIndex.put(index, gid);
var val = glyf[gid];
unicodeToGlyph.put(index, val);
if (!glyphToUnicode.containsKey(val)) glyphToUnicode.put(val, index);
}
}
});
}
executor.shutdown();
try {
boolean b = executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e("queryTTF", "建立Unicode&Glyph映射表出错: " + e);
}
}
// /** // /**
// * 获取字体信息 (1=字体名称) // * 获取字体信息 (1=字体名称)
// * // *
@ -530,6 +536,10 @@ public class QueryTTF {
// return "error"; // return "error";
// } // }
public String getGlyfById(int gid) {
return glyf[gid];
}
/** /**
* 使用Unicode值查找轮廓索引 * 使用Unicode值查找轮廓索引
* *
@ -542,7 +552,7 @@ public class QueryTTF {
int fmtKey = 0; int fmtKey = 0;
for (var item : pps) { for (var item : pps) {
for (CmapRecord record : Cmap.records) { for (CmapRecord record : Cmap.records) {
if ((item[0] == record.platformID) && (item[1] == record.encodingID)) { if ((item[0] == record.platformID) && (item[1] == record.platformSpecificID)) {
fmtKey = record.offset; fmtKey = record.offset;
break; break;
} }