Compare commits

...

5 Commits

Author SHA1 Message Date
Antecer
fd3d96fa74 优化循环查询字体unicode的效率 2024-04-29 19:29:23 +08:00
Antecer
ad1992d493 Merge branch 'master' of github.com:gedoor/legado 2024-04-29 19:05:10 +08:00
Antecer
fa3fc01080 优化字体处理效率 2024-04-29 19:05:00 +08:00
Antecer
1e5ea697ad 删除多余的缓存 2024-04-29 19:04:10 +08:00
Antecer
6cc3d3a085 update .gitignore 2024-04-29 14:13:48 +08:00
8 changed files with 330 additions and 449 deletions

16
.idea/.gitignore vendored
View File

@ -1,16 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/
# Custom ignored
/caches/
/dictionaries/
/modules/
/libraries/
/*.xml
# GitHub Copilot persisted chat sessions
/copilot/chatSessions

View File

@ -1 +0,0 @@
legado

View File

@ -1,123 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -1,20 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ConstantConditions" enabled="true" level="WARNING" enabled_by_default="true">
<option name="SUGGEST_NULLABLE_ANNOTATIONS" value="false" />
<option name="DONT_REPORT_TRUE_ASSERT_STATEMENTS" value="false" />
</inspection_tool>
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="name" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="NonAsciiCharacters" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

View File

@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Default" />
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@ -768,15 +768,7 @@ interface JsExtensions : JsEncodeUtils {
var qTTF = CacheManager.getQueryTTF(key) var qTTF = CacheManager.getQueryTTF(key)
if (qTTF != null) return qTTF if (qTTF != null) return qTTF
val font: ByteArray? = when { val font: ByteArray? = when {
str.isAbsUrl() -> { str.isAbsUrl() -> AnalyzeUrl(str, source = getSource()).getByteArray()
var x = CacheManager.getByteArray(key)
if (x == null) {
x = AnalyzeUrl(str, source = getSource()).getByteArray()
CacheManager.put(key, x)
}
x
}
str.isContentScheme() -> Uri.parse(str).readBytes(appCtx) str.isContentScheme() -> Uri.parse(str).readBytes(appCtx)
str.startsWith("/storage") -> File(str).readBytes() str.startsWith("/storage") -> File(str).readBytes()
else -> base64DecodeToByteArray(str) else -> base64DecodeToByteArray(str)

View File

@ -1,15 +1,19 @@
package io.legado.app.model.analyzeRule; package io.legado.app.model.analyzeRule;
import org.apache.commons.lang3.tuple.Pair; import android.util.Log;
import org.apache.commons.lang3.tuple.Triple;
import java.nio.charset.Charset; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; 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", "unused"})
public class QueryTTF { public class QueryTTF {
@ -129,7 +133,7 @@ public class QueryTTF {
public int length; public int length;
public int language; public int language;
public int numGroups; public int numGroups;
public List<Triple<Integer, Integer, Integer>> groups; public List<int[]> groups;
} }
private static class GlyfLayout { private static class GlyfLayout {
@ -147,103 +151,335 @@ public class QueryTTF {
} }
private static class ByteArrayReader { private static class ByteArrayReader {
public int index; private final byte[] buffer;
public byte[] buffer; public final ByteBuffer byteBuffer;
public ByteArrayReader(byte[] buffer, int index) { public ByteArrayReader(byte[] buffer, int index) {
this.buffer = buffer; 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) { public void position(int index) {
long result = 0; byteBuffer.position(index); // 设置起始索引
for (long i = 0; i < len; ++i) {
result <<= 8;
result |= buffer[index++] & 0xFF;
} }
return result;
public int position() {
return byteBuffer.position();
} }
public long ReadUInt64() { public long ReadUInt64() {
return ReadUIntX(8); return byteBuffer.getLong();
} }
public int ReadUInt32() { public int ReadUInt32() {
return (int) ReadUIntX(4); return byteBuffer.getInt();
}
public int ReadInt32() {
return byteBuffer.getInt();
} }
public int ReadUInt16() { public int ReadUInt16() {
return (int) ReadUIntX(2); return byteBuffer.getShort() & 0xFFFF;
} }
public short ReadInt16() { public short ReadInt16() {
return (short) ReadUIntX(2); return byteBuffer.getShort();
} }
public short ReadUInt8() { public short ReadUInt8() {
return (short) ReadUIntX(1); return (short) (byteBuffer.get() & 0xFF);
} }
public byte ReadInt8() {
public String ReadStrings(int len, Charset charset) { return byteBuffer.get();
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() { public byte[] ReadByteArray(int len) {
return buffer[index++]; if (len < 0) throw new IllegalArgumentException("Length must not be negative");
} byte[] result = new byte[len];
byteBuffer.get(result);
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; return result;
} }
public int[] GetUInt16Array(int len) { public short[] ReadInt16Array(int len) {
int[] result = len > 0 ? new int[len] : null; if (len < 0) throw new IllegalArgumentException("Length must not be negative");
for (int i = 0; i < len; ++i) result[i] = ReadUInt16(); var result = new short[len];
for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort();
return result; return result;
} }
public short[] GetInt16Array(int len) { public int[] ReadUInt16Array(int len) {
short[] result = len > 0 ? new short[len] : null; if (len < 0) throw new IllegalArgumentException("Length must not be negative");
for (int i = 0; i < len; ++i) result[i] = ReadInt16(); var result = new int[len];
for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort() & 0xFFFF;
return result; return result;
} }
} }
private final ByteArrayReader fontReader;
private final Header fileHeader = new Header(); 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 NameLayout name = new NameLayout();
private final HeadLayout head = new HeadLayout(); private final HeadLayout head = new HeadLayout();
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 List<String> glyf = new LinkedList<>(); private final ConcurrentHashMap<Integer, String> glyf = new ConcurrentHashMap<>();
@SuppressWarnings("unchecked") private final int[][] pps = new int[][]{
private final Pair<Integer, Integer>[] pps = new Pair[]{ {3, 10},
Pair.of(3, 10), {0, 4},
Pair.of(0, 4), {3, 1},
Pair.of(3, 1), {1, 0},
Pair.of(1, 0), {0, 3},
Pair.of(0, 3), {0, 1}
Pair.of(0, 1)
}; };
public final Map<Integer, String> unicodeToGlyph = new HashMap<>(); public final Map<Integer, String> unicodeToGlyph = new HashMap<>();
public final Map<String, Integer> glyphToUnicode = new HashMap<>(); public final Map<String, Integer> glyphToUnicode = new HashMap<>();
public final Map<Integer, Integer> unicodeToGlyphIndex = 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;
final var reader = new ByteArrayReader(buffer, dataTable.offset + loca.get(index));
executor.submit(() -> {
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);
}
}
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(byte[] buffer) {
fontReader = new ByteArrayReader(buffer, 0); var fontReader = new ByteArrayReader(buffer, 0);
Log.i("queryTTF", "读文件头");
// 获取文件头 // 获取文件头
fileHeader.majorVersion = fontReader.ReadUInt16(); fileHeader.majorVersion = fontReader.ReadUInt16();
fileHeader.minorVersion = fontReader.ReadUInt16(); fileHeader.minorVersion = fontReader.ReadUInt16();
@ -254,215 +490,45 @@ public class QueryTTF {
// 获取目录 // 获取目录
for (int i = 0; i < fileHeader.numOfTables; ++i) { for (int i = 0; i < fileHeader.numOfTables; ++i) {
Directory d = new Directory(); 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.checkSum = fontReader.ReadUInt32();
d.offset = fontReader.ReadUInt32(); d.offset = fontReader.ReadUInt32();
d.length = fontReader.ReadUInt32(); d.length = fontReader.ReadUInt32();
directorys.add(d); directorys.put(d.tag, 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));
}
}
} }
// 建立Unicode&Glyph双向表 Log.i("queryTTF", "解析表 name"); // 字体信息,包含版权名称作者等...
for (int key = 0; key < 130000; ++key) { readNameTable(buffer);
// if (key == 0xFF) key = 0x3400; Log.i("queryTTF", "解析表 head"); // 获取 head.indexToLocFormat
int gid = queryGlyfIndex(key); readHeadTable(buffer);
if (gid >= glyf.size()) continue; Log.i("queryTTF", "解析表 cmap"); // Unicode编码->轮廓索引 对照表
unicodeToGlyphIndex.put(key, gid); readCmapTable(buffer);
var val = glyf.get(gid); Log.i("queryTTF", "解析表 loca"); // 轮廓数据偏移地址表
unicodeToGlyph.put(key, val); readLocaTable(buffer);
if (glyphToUnicode.containsKey(val)) continue; Log.i("queryTTF", "解析表 maxp"); // 获取 maxp.numGlyphs 字体轮廓数量
glyphToUnicode.put(val, key); readMaxpTable(buffer);
} Log.i("queryTTF", "解析表 glyf"); // 字体轮廓数据表,需要解析loca,maxp表后计算
readGlyfTable(buffer);
Log.i("queryTTF", "建立Unicode&Glyph映射表");
makeHashTable();
Log.i("queryTTF", "字体处理完成");
} }
/** // /**
* 获取字体信息 (1=字体名称) // * 获取字体信息 (1=字体名称)
* // *
* @param nameId 传入十进制字体信息索引 // * @param nameId 传入十进制字体信息索引
* @return 返回查询结果字符串 // * @return 返回查询结果字符串
*/ // */
public String getNameById(int nameId) { // public String getNameById(int nameId) {
for (Directory Temp : directorys) { // fontReader.index = Objects.requireNonNull(directorys.get("name")).offset;
if (!Temp.tag.equals("name")) continue; // for (NameRecord record : name.records) {
fontReader.index = Temp.offset; // if (record.nameID != nameId) continue;
break; // fontReader.index += name.stringOffset + record.offset;
} // return fontReader.ReadStrings(record.length, record.platformID == 1 ? StandardCharsets.UTF_8 : StandardCharsets.UTF_16BE);
for (NameRecord record : name.records) { // }
if (record.nameID != nameId) continue; // return "error";
fontReader.index += name.stringOffset + record.offset; // }
return fontReader.ReadStrings(record.length, record.platformID == 1 ? StandardCharsets.UTF_8 : StandardCharsets.UTF_16BE);
}
return "error";
}
/** /**
* 使用Unicode值查找轮廓索引 * 使用Unicode值查找轮廓索引
@ -472,10 +538,11 @@ public class QueryTTF {
*/ */
public int queryGlyfIndex(int unicode) { public int queryGlyfIndex(int unicode) {
if (unicode == 0) return 0; if (unicode == 0) return 0;
int fmtKey = 0; int fmtKey = 0;
for (Pair<Integer, Integer> item : pps) { for (var item : pps) {
for (CmapRecord record : Cmap.records) { 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; fmtKey = record.offset;
break; break;
} }
@ -512,10 +579,10 @@ public class QueryTTF {
if (0 <= index && index < tab.glyphIdArray.length) glyfID = tab.glyphIdArray[index]; if (0 <= index && index < tab.glyphIdArray.length) glyfID = tab.glyphIdArray[index];
} else if (fmt == 12) { } else if (fmt == 12) {
CmapFormat12 tab = (CmapFormat12) table; 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++) { for (int i = 0; i < tab.numGroups; i++) {
if (tab.groups.get(i).getLeft() <= unicode && unicode <= tab.groups.get(i).getMiddle()) { if (tab.groups.get(i)[0] <= unicode && unicode <= tab.groups.get(i)[1]) {
glyfID = tab.groups.get(i).getRight() + unicode - tab.groups.get(i).getLeft(); glyfID = tab.groups.get(i)[2] + unicode - tab.groups.get(i)[0];
break; break;
} }
} }
@ -564,14 +631,8 @@ public class QueryTTF {
*/ */
public String getHexFromBytes(byte[] glyph) { public String getHexFromBytes(byte[] glyph) {
if (glyph == null) return ""; if (glyph == null) return "";
StringBuilder sb = new StringBuilder(); StringBuilder hexString = new StringBuilder();
for (byte b : glyph) { for (byte b : glyph) hexString.append(String.format("%02X", b));
String hex = Integer.toHexString(b); return hexString.toString();
if (hex.length() == 1) {
sb.append("0");//当16进制为个位数时在前面补0
}
sb.append(hex);//将16进制加入字符串
}
return sb.toString().toUpperCase();
} }
} }